home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / subject.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-12-05  |  88.6 KB  |  3,366 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     subject.c
  4.  
  5.     This module handles subject windows.
  6.     
  7.     Copyright © 1994, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <script.h>
  14. #include <ctype.h>
  15. #include <Script.h>
  16. #include <stdlib.h>
  17.  
  18. #include "glob.h"
  19. #include "subject.h"
  20. #include "child.h"
  21. #include "mark.h"
  22. #include "news.h"
  23. #include "newswatcher.h"
  24. #include "dialog.h"
  25. #include "status.h"
  26. #include "menus.h"
  27. #include "next.h"
  28. #include "ldef.h"
  29. #include "article.h"
  30. #include "collapse.h"
  31. #include "drawutil.h"
  32. #include "memutil.h"
  33. #include "listutil.h"
  34. #include "windutil.h"
  35. #include "thread.h"
  36. #include "tescroll.h"
  37. #include "cancel.h"
  38. #include "header.h"
  39. #include "wind.h"
  40. #include "strutil.h"
  41. #include "message.h"
  42. #include "fileutil.h"
  43. #include "group.h"
  44. #include "olddrag.h"
  45. #include "dragutil.h"
  46. #include "print.h"
  47. #include "key.h"
  48. #include "teutil.h"
  49. #include "search.h"
  50. #include "ic.h"
  51.  
  52.  
  53.  
  54. #define kMinWindowWidth        320                /* minimum window width */
  55.  
  56. #define kExtractBinariesManuallyDlg            149
  57. #define kExtractBinariesManuallyLabelItem    3
  58. #define kExtractBinariesManuallyListItem    4
  59.  
  60.  
  61.  
  62. static ListHandle gExtractBinariesManuallyList;
  63.  
  64. static Boolean gFirstListClickCall;        /* true if first call to ClickLoop function */
  65. static OSErr gClickLoopErr;                /* click loop error code */
  66. static WindowPtr gDragSrcWindow;        /* pointer to drag source window  */
  67. static FSSpec gDragTheFile;                /* file spec for saved dragged subjects */
  68. static Boolean gDraggedToFinder;        /* true if subjects dragged to Finder */
  69. static short gDragDestRow;                /* destination row when dragging subjects
  70.                                            in extract binaries manually dialog */
  71.  
  72. static ListClickLoopUPP gExtractBinariesManuallyClickLoopUPP;
  73. static ListClickLoopUPP gOldListClickLoopUPP;
  74. static ListClickLoopUPP gSubjectListClickLoopUPP;
  75. static DragTrackingHandlerUPP gExtractBinariesManuallyHandleTrackingUPP;
  76. static DragReceiveHandlerUPP gExtractBinariesManuallyHandleReceiveUPP;
  77. static DragSendDataUPP gDragSubjectsSendProcUPP;
  78. static ModalFilterUPP gExtractBinariesManuallyDialogFilterUPP;
  79. static UserItemUPP gExtractBinariesManuallyDlgLabelUserItemUPP;
  80. static UserItemUPP gExtractBinariesManuallyDlgListUserItemUPP;
  81.  
  82.  
  83.  
  84. /*----------------------------------------------------------------------------
  85.     GetSubjects
  86.     
  87.     Get a range of subject headers from the news server and store them in
  88.     a subject array.
  89.     
  90.     Entry:    newsGroup = name of the group.
  91.             first = first article number to get.
  92.             last = last article number to get.
  93.             subjectArray = handle to subject array.
  94.             index = index in subject array to store first subject.
  95.             strings = handle to strings block.
  96.             *nextStringOffset = offset of next available location in
  97.                 strings block.
  98.     
  99.     Exit:    function result = error code.
  100.             *numFetched = number of subjects fetched.
  101.             *nextStringOffset updated.
  102.     
  103.     The caller must preallocate memory in the subject array for at least
  104.     (last-first+1) new subjects.
  105.     
  106.     If the group does not exist the function result is true and 
  107.     *numFetched=0.
  108. ----------------------------------------------------------------------------*/
  109.  
  110. static OSErr GetSubjects (char *newsGroup, long first, long last, 
  111.     TSubject **subjectArray, short index,
  112.     Handle strings, long *nextStringOffset,
  113.     short *numFetched)
  114. {
  115.     THeader **headers = nil; 
  116.     long numHeaders;
  117.     TSubject theSubject;
  118.     THeader *pHeader, *pHeaderEnd;
  119.     TSubject *pSubject;
  120.     Handle str = nil;
  121.     long strLen;
  122.     long offset;
  123.     OSErr err = noErr;
  124.  
  125.     err = GetHeaders(newsGroup, "SUBJECT", first, last, &headers, &str, &numHeaders);
  126.     if (err != noErr) return err;
  127.     if (headers == nil) {
  128.         *numFetched = 0;
  129.         return noErr;
  130.     }
  131.     
  132.     strLen = MyGetHandleSize(str);
  133.     offset = *nextStringOffset;
  134.     err = MyHandAndHand(str, strings);
  135.     if (err != noErr) goto exit;
  136.     *nextStringOffset += strLen;
  137.     
  138.     pHeaderEnd = *headers + numHeaders;
  139.     pSubject = *subjectArray + index;
  140.     for (pHeader = *headers; pHeader < pHeaderEnd; pHeader++, pSubject++, index++) {
  141.         theSubject.subjectOffset = pHeader->offset + offset;
  142.         theSubject.authorOffset = -1;
  143.         theSubject.number = pHeader->number;
  144.         theSubject.collapsed = gPrefs.showThreadsCollapsed;
  145.         theSubject.read = false;
  146.         theSubject.inList = true;
  147.         theSubject.drawTriangleFilled = false;
  148.         theSubject.onlyRedrawTriangle = false;
  149.         theSubject.onlyRedrawCheck = false;
  150.         *pSubject = theSubject;
  151.     }
  152.  
  153.     MyDisposeHandle(headers);
  154.     MyDisposeHandle(str);
  155.     *numFetched = numHeaders;
  156.     return noErr;
  157.     
  158. exit:
  159.  
  160.     MyDisposeHandle(str);
  161.     MyDisposeHandle(headers);
  162.     return err;
  163. }
  164.  
  165.  
  166.  
  167. /*----------------------------------------------------------------------------
  168.     GetAuthors
  169.     
  170.     Get a range of author headers from the news server and store them in
  171.     a subject array.
  172.     
  173.     Entry:    newsGroup = name of the group.
  174.             first = first article number to get.
  175.             last = last article number to get.
  176.             subjectArray = handle to subject array.
  177.             index = starting index in subject array.
  178.             num = number of subject records.
  179.             strings = handle to strings block.
  180.             *nextStringOffset = offset of next available location in
  181.                 strings block.
  182.     
  183.     Exit:    function result = error code.
  184.             *nextStringOffset updated.
  185.     
  186.     GetSubjects must be called before GetAuthors to initialize the
  187.     subject array elements.
  188. ----------------------------------------------------------------------------*/
  189.  
  190. static OSErr GetAuthors (char *newsGroup, long first, long last, 
  191.     TSubject **subjectArray, short index, short num,
  192.     Handle strings, long *nextStringOffset)
  193. {
  194.     THeader **headers = nil; 
  195.     long numHeaders;
  196.     THeader *pHeader, *pHeaderEnd;
  197.     TSubject *pSubject, *pSubjectEnd;
  198.     Handle str = nil;
  199.     long strLen;
  200.     long offset;
  201.     OSErr err = noErr;
  202.  
  203.     err = GetHeaders(newsGroup, "From", first, last, &headers, &str, &numHeaders);
  204.     if (err != noErr) return err;
  205.     if (headers == nil) return noErr;
  206.     
  207.     strLen = MyGetHandleSize(str);
  208.     offset = *nextStringOffset;
  209.     err = MyHandAndHand(str, strings);
  210.     if (err != noErr) goto exit;
  211.     *nextStringOffset += strLen;
  212.     
  213.     pHeader = *headers;
  214.     pHeaderEnd = *headers + numHeaders;
  215.     pSubject = *subjectArray + index;
  216.     pSubjectEnd = pSubject + num;
  217.     while (pHeader < pHeaderEnd && pSubject < pSubjectEnd) {
  218.         if (pHeader->number < pSubject->number) {
  219.             pHeader++;
  220.         } else if (pHeader->number > pSubject->number) {
  221.             pSubject++;
  222.         } else {
  223.             pSubject->authorOffset = pHeader->offset + offset;
  224.             pHeader++;
  225.             pSubject++;
  226.         }
  227.     }
  228.     
  229.     MyDisposeHandle(headers);
  230.     MyDisposeHandle(str);
  231.     return noErr;
  232.     
  233. exit:
  234.  
  235.     MyDisposeHandle(headers);
  236.     MyDisposeHandle(str);
  237.     return err;
  238. }
  239.  
  240.  
  241.  
  242. /*----------------------------------------------------------------------------
  243.     GetSubjectsAndAuthorsFromNet 
  244.     
  245.     Get unread subjects and authors for a group from the NNTP server.
  246.     
  247.     Entry:    groupName = the group name.
  248.             theGroup = pointer to the group record.
  249.             groupKind = kind of group (kFullGroup, kNewGroup, or kUserGroup).
  250.             maxFetch = maximum number of articles to fetch.
  251.             
  252.     Exit:    function result = error code.
  253.             *subjectArray = handle to subject array, with the fields
  254.                 subjectOffset, authorOffset, number, read, and inList initialized.
  255.             *numSubjects = number of subjects fetched.
  256.             *subjectStrings = handle to subject strings.
  257.             *firstFetched = article number of first article fetched.
  258.             
  259.     If the group does not exist, the function result is true and 
  260.     *numSubjects = 0. In other words, a deleted group is treated the
  261.     same as a group with no unread articles.
  262. ----------------------------------------------------------------------------*/
  263.  
  264. static OSErr GetSubjectsAndAuthorsFromNet (char *groupName, TGroup *theGroup, 
  265.     TGroupWindowKind groupKind, short maxFetch,
  266.     TSubject ***subjectArray, short *numSubjects, 
  267.     Handle *subjectStrings, long *firstFetched)
  268. {
  269.     TSubject **array = nil;
  270.     short index = 0;
  271.     Handle strings = nil;
  272.     OSErr err = noErr;
  273.     long nextStringOffset = 0;
  274.     TUnread **unread;
  275.     long first, last, numToFetch, totNumToFetch;
  276.     short numAllocated;
  277.     short numFetched;
  278.     
  279.     err = MyNewHandle(100*sizeof(TSubject), &array);
  280.     if (err != noErr) goto exit;
  281.     numAllocated = 100;
  282.     err = MyNewHandle(0, &strings);
  283.     if (err != noErr) goto exit;
  284.     
  285.     if (groupKind == kUserGroup) {
  286.         unread = theGroup->unread;
  287.         totNumToFetch = theGroup->numUnread;
  288.         first = (**unread).firstUnread;
  289.         last = (**unread).lastUnread;
  290.         if (totNumToFetch > maxFetch) {
  291.             while (unread != nil) {
  292.                 totNumToFetch -= last - first + 1;
  293.                 if (totNumToFetch <= maxFetch) {
  294.                     first = last - (maxFetch - totNumToFetch) + 1;
  295.                     break;
  296.                 }
  297.                 unread = (**unread).next;
  298.                 if (unread != nil) {
  299.                     first = (**unread).firstUnread;
  300.                     last = (**unread).lastUnread;
  301.                 }
  302.             }
  303.         }
  304.         *firstFetched = first;
  305.         while (unread != nil) {
  306.             numToFetch = last - first + 1;
  307.             if (index + numToFetch > numAllocated) {
  308.                 numAllocated = index + numToFetch + 100;
  309.                 err = MySetHandleSize(array, numAllocated*sizeof(TSubject));
  310.                 if (err != noErr) goto exit;
  311.             }
  312.             err = GetSubjects(groupName, first, last, array, index, strings,
  313.                 &nextStringOffset, &numFetched);
  314.             if (err != noErr) goto exit;
  315.             if (gPrefs.showAuthors) {
  316.                 err = GetAuthors(groupName, first, last, array, index, numFetched, strings,
  317.                     &nextStringOffset);
  318.                 if (err != noErr) goto exit;
  319.             }
  320.             index += numFetched;
  321.             unread = (**unread).next;
  322.             if (unread != nil) {
  323.                 first = (**unread).firstUnread;
  324.                 last = (**unread).lastUnread;
  325.             }
  326.         }
  327.     } else {
  328.         first = theGroup->firstMess;
  329.         last = theGroup->lastMess;
  330.         numToFetch = last - first + 1;
  331.         if (numToFetch > maxFetch) {
  332.             numToFetch = maxFetch;
  333.             first = last - numToFetch + 1;
  334.         }
  335.         *firstFetched = first;
  336.         if (numToFetch > numAllocated) {
  337.             numAllocated = numToFetch;
  338.             err = MySetHandleSize(array, numAllocated*sizeof(TSubject));
  339.             if (err != noErr) goto exit;
  340.         }
  341.         err = GetSubjects(groupName, first, last, array, index, strings,
  342.             &nextStringOffset, &numFetched);
  343.         if (err != noErr) goto exit;
  344.         if (gPrefs.showAuthors) {
  345.             err = GetAuthors(groupName, first, last, array, index, numFetched, strings,
  346.                 &nextStringOffset);
  347.             if (err != noErr) goto exit;
  348.         }
  349.         index = numFetched;
  350.     }
  351.     
  352.     MySetHandleSize(array, index*sizeof(TSubject));
  353.     MySetHandleSize(strings, nextStringOffset);
  354.  
  355.     *subjectArray = array;
  356.     *numSubjects = index;
  357.     *subjectStrings = strings;
  358.     return noErr;
  359.  
  360. exit:
  361.  
  362.     MyDisposeHandle(array);
  363.     MyDisposeHandle(strings);
  364.     return err;
  365. }
  366.  
  367.  
  368.  
  369. /*----------------------------------------------------------------------------
  370.     FixHeight 
  371.     
  372.     Round down window height to an exact multiple of lines.
  373.             
  374.     Entry:    wind = pointer to subject window.
  375.             *height = window height.
  376.             
  377.     Exit:    *height = adjusted window height
  378. ----------------------------------------------------------------------------*/
  379.  
  380. static void FixHeight (WindowPtr wind, short *height)
  381. {
  382.     TWindow **info;
  383.     short panelHeight, lineHeight, adjust;
  384.  
  385.     info = (TWindow**)GetWRefCon(wind);
  386.     panelHeight = (**info).panelHeight;
  387.     lineHeight = GetFontLineHeight(wind);
  388.     adjust = panelHeight + 15;
  389.     *height = (*height - adjust) / lineHeight * lineHeight + adjust;
  390. }
  391.  
  392.  
  393.  
  394. /*----------------------------------------------------------------------------
  395.     MinHeight 
  396.     
  397.     Compute the minimum height of a subject window.
  398.             
  399.     Entry:    wind = pointer to subject window.
  400. ----------------------------------------------------------------------------*/
  401.  
  402. static short MinHeight (WindowPtr wind)
  403. {
  404.     TWindow **info;
  405.     short lineHeight, height, extra;
  406.     
  407.     info = (TWindow**)GetWRefCon(wind);
  408.     lineHeight = GetFontLineHeight(wind);
  409.     extra = lineHeight + 15;
  410.     if (extra < 65) extra = 65 + lineHeight;
  411.     height = (**info).panelHeight + extra;
  412.     FixHeight(wind, &height);
  413.     return height;
  414. }
  415.  
  416.  
  417.  
  418. /*----------------------------------------------------------------------------
  419.     Scroll 
  420.     
  421.     Scroll a subject window.
  422.             
  423.     Entry:    wind = pointer to subject window.
  424.             part = part code.
  425. ----------------------------------------------------------------------------*/
  426.  
  427. static void Scroll (WindowPtr wind, short part)
  428. {
  429.     TWindow **info;
  430.     ListHandle theList;
  431.     short height, numCells;
  432.     Cell theCell;
  433.     
  434.     info = (TWindow**)GetWRefCon(wind);
  435.     theList = (**info).theList;
  436.     height = (**theList).visible.bottom - (**theList).visible.top;
  437.     numCells = (**theList).dataBounds.bottom;
  438.     switch (part) {
  439.         case inUpButton:
  440.         case inDownButton:
  441.             SetPt(&theCell, 0, 0);
  442.             if (part == inUpButton) {
  443.                 theCell.v = GetFirstSelectedCell(theList);
  444.                 theCell.v--;
  445.                 if (theCell.v < 0) theCell.v = 0;
  446.             } else { 
  447.                 theCell.v = GetLastSelectedCell(theList);
  448.                 theCell.v++;
  449.                 if (theCell.v >= numCells) theCell.v = numCells-1;
  450.             }
  451.             SelectSingleListItem(theList, theCell);
  452.             HandleUpdate(wind);
  453.             MyLAutoScroll(theList);
  454.             break;
  455.         case inPageUp:
  456.             MyLScroll(-(height - 1), theList);
  457.             break;
  458.         case inPageDown:
  459.             MyLScroll(height - 1, theList);
  460.             break;
  461.         case kScrollToHome:
  462.             MyLScroll(-numCells, theList);
  463.             break;
  464.         case kScrollToEnd:
  465.             MyLScroll(numCells, theList);
  466.             break;
  467.     }
  468. }
  469.  
  470.  
  471.  
  472. /*----------------------------------------------------------------------------
  473.     ResizeContents 
  474.     
  475.     Adjust a group window's contents after a window size change (grow
  476.     or zoom).
  477.             
  478.     Entry:    wind = pointer to group window.
  479. ----------------------------------------------------------------------------*/
  480.  
  481. static void ResizeContents (WindowPtr wind)
  482. {
  483.     TWindow **info;
  484.     short width, height, panelHeight;
  485.     ListHandle theList;
  486.     Point x;
  487.  
  488.     info = (TWindow**)GetWRefCon(wind);
  489.     panelHeight = (**info).panelHeight;
  490.     theList = (**info).theList;
  491.     width = wind->portRect.right;
  492.     height = wind->portRect.bottom;
  493.  
  494.     LSize(width-15, height-15-panelHeight, theList);
  495.     SetPt(&x, width-15, (**theList).cellSize.v);
  496.     LCellSize(x, theList);
  497.     
  498.     InvalRect(&wind->portRect);
  499.     
  500.     RecordNewLockedWindowPosition(wind, &gPrefs.subjectWindLocn);
  501. }
  502.  
  503.  
  504.  
  505. /*----------------------------------------------------------------------------
  506.     MakeNewWindow 
  507.     
  508.     Create a new subject window.
  509.     
  510.     Entry:    title = window title.
  511.             
  512.     Exit:    function result = error code.
  513.             *theWindow = pointer to new window.
  514. ----------------------------------------------------------------------------*/
  515.  
  516. static OSErr MakeNewWindow (StringPtr title, WindowPtr *theWindow)
  517. {
  518.     short width, height;
  519.     WindowPtr wind = nil;
  520.     TWindow **info;
  521.     ListHandle theList;
  522.     Point thePt;
  523.     Rect listRect, sizeRect;
  524.     short wid, checkMarkWidth, panelHeight;
  525.     short tHeight, tWidth, x;
  526.     FontInfo fInfo;
  527.     TextStyle savedStyle;
  528.     OSErr err;
  529.     GrafPtr port;
  530.     
  531.     GetPort(&port);
  532.  
  533.     err = CreateNewWindow(kSubject, title, gPrefs.listFont, gPrefs.listSize, &wind);
  534.     if (err != noErr) return err;
  535.     SetPort(wind);
  536.     info = (TWindow**)GetWRefCon(wind);
  537.     panelHeight = GetFontLineHeight(wind) + 9;
  538.     (**info).panelHeight = panelHeight;
  539.     (**info).authorsShown = gPrefs.showAuthors;
  540.     width = kMinWindowWidth;
  541.     height = MinHeight(wind);
  542.     RestoreLockedWindowPosition(wind, width, height, gPrefs.subjectWindPosLocked,
  543.         &gPrefs.subjectWindLocn);
  544.     width = wind->portRect.right;
  545.     height = wind->portRect.bottom;
  546.  
  547.     SetPt(&thePt, 0, 0);
  548.     SetRect(&sizeRect, 0, 0, 1, 0);
  549.     listRect = wind->portRect;
  550.     listRect.top += panelHeight;
  551.     listRect.right -= 15;
  552.     listRect.bottom -= 15;
  553.     theList = MyLNew(&listRect, &sizeRect, thePt, kLDEFProc, wind, false, true, false, true);
  554.     (**theList).indent.h = 4;
  555.     (**theList).selFlags |= lNoNilHilite;
  556.     (**theList).refCon = (long)gListDefFuncUPP;
  557.     (**info).theList = theList;
  558.     
  559.     GetFontInfo(&fInfo);
  560.     wid = CharWidth('9');
  561.     GetPortTextStyle(&savedStyle);
  562.     TextFont(systemFont);
  563.     checkMarkWidth = CharWidth(checkMark);
  564.     SetPortTextStyle(&savedStyle);
  565.     tWidth = fInfo.ascent;
  566.     if ((tWidth & 1) == 1) tWidth--;
  567.     tHeight = tWidth >> 1; 
  568.     (**info).minusSignHCoord = ((tWidth - CharWidth('-')) >> 1) + 2;
  569.     (**info).threadCountHCoord = x = 3*wid + tWidth;
  570.     (**info).checkHCoord = x = x + 2*wid;
  571.     if ((**info).authorsShown) {
  572.         (**info).authorHCoord = x = x + 2*checkMarkWidth;
  573.         (**info).authorWidth = 16*wid;
  574.         (**info).subjectHCoord = x + 18*wid;
  575.     } else {
  576.         (**info).subjectHCoord = (**info).authorHCoord = x = x + 2*checkMarkWidth;
  577.     }
  578.     (**info).collapseTriangle = OpenPoly();
  579.         MoveTo(0,0);
  580.         LineTo(0,tWidth);
  581.         LineTo(tHeight,tHeight);
  582.         LineTo(0,0);
  583.     ClosePoly();
  584.     (**info).expandTriangle = OpenPoly();
  585.         MoveTo(0,0);
  586.         LineTo(tWidth,0);
  587.         LineTo(tHeight,tHeight);
  588.         LineTo(0,0);
  589.     ClosePoly();
  590.     
  591.     (**info).autoExpandedThread = -1;
  592.     
  593.     *theWindow = wind;
  594.     SetPort(port);
  595.     return noErr;
  596. }
  597.  
  598.  
  599.  
  600. /*----------------------------------------------------------------------------
  601.     RedrawSubjectWindowUnreadCount
  602.     
  603.     Force the panel are unread article count to be updated in a subject window.
  604.         
  605.     Entry:    wind = pointer to suject list window.
  606. ----------------------------------------------------------------------------*/
  607.  
  608. void RedrawSubjectWindowUnreadCount (WindowPtr wind)
  609. {
  610.     TWindow **info;
  611.     GrafPtr port;
  612.     Rect r;
  613.  
  614.     GetPort(&port);
  615.     SetPort(wind);
  616.     info = (TWindow**)GetWRefCon(wind);
  617.     r = wind->portRect;
  618.     r.bottom = (**info).panelHeight - 4;
  619.     InvalRect(&r);
  620.     SetPort(port);
  621. }
  622.  
  623.  
  624.  
  625. /*----------------------------------------------------------------------------
  626.     OpenGroupCell 
  627.     
  628.     Open one subject window for a cell in a group list window.
  629.     
  630.     Entry:    groupWind = pointer to group list window.
  631.             theCell = the cell in the group list window to be opened.
  632.             maxFetch = maximum number of articles to fetch.
  633.             
  634.     Exit:    function result = error code.
  635.             *hasArts = true if the group exists, has articles, and the window
  636.                 was opened.
  637. ----------------------------------------------------------------------------*/
  638.  
  639. OSErr OpenGroupCell (WindowPtr groupWind, Cell theCell, short maxFetch, Boolean *hasArts)
  640. {
  641.     WindowPtr wind = nil;
  642.     TWindow **groupInfo, **info;
  643.     ListHandle theList;
  644.     TGroup **groupArray, theGroup;
  645.     TSubject **subjectArray = nil;
  646.     short numSubjects = 0;
  647.     Handle subjectStrings = nil;
  648.     CStr255 groupName;
  649.     short cellDataLen, index;
  650.     TGroupWindowKind groupKind;
  651.     Cell childCell;
  652.     CStr255 statusMsg;
  653.     long firstFetched;
  654.     OSErr err = noErr;
  655.     Boolean groupExists;
  656.     GrafPtr port;
  657.     
  658.     GetPort(&port);
  659.     
  660.     *hasArts = true;
  661.     
  662.     /* Check to see if the subject window is already open. Bring it
  663.        to the front if it is. */
  664.  
  665.     if ((wind = FindChild(groupWind, theCell)) != nil) {
  666.         MySelectWindow(wind);
  667.         return noErr;
  668.     }
  669.     
  670.     /* Initialize. */
  671.     
  672.     groupInfo = (TWindow**)GetWRefCon(groupWind);
  673.     groupKind = (**groupInfo).groupKind;
  674.     theList = (**groupInfo).theList;
  675.     groupArray = (**groupInfo).groupArray;
  676.     cellDataLen = 2;
  677.     LGetCell(&index, &cellDataLen, theCell, theList);
  678.     theGroup = (*groupArray)[index];
  679.     strcpy(groupName, *gGroupNames + theGroup.nameOffset);
  680.     if (groupKind == kUserGroup && theGroup.numUnread == 0) goto exit1;
  681.     
  682.     if (groupKind == kUserGroup) (**groupInfo).changed = true;
  683.     
  684.     /* Display the status window. */    
  685.     
  686.     if (gPrefs.showAuthors) {
  687.         GetCString(kStrGettingSubjectsAndAuthorsStatusMsg, statusMsg);
  688.     } else {
  689.         GetCString(kStrGetSubjectsStatusMsg, statusMsg);
  690.     }
  691.     strcat(statusMsg, groupName);
  692.     statusMsg[255] = 0;
  693.     err = DisplayStatusMessage(statusMsg);
  694.     if (err != noErr) goto exit2;
  695.     
  696.     /* If we are opening a cell in the full group list window or the new groups
  697.        window, first call GetGroupArticleRange to get the [low,high] article range
  698.        for the group. */
  699.  
  700.     if (groupKind != kUserGroup) {
  701.         err = GetGroupArticleRange(&theGroup, &groupExists);
  702.         if (err != noErr) goto exit2;
  703.         if (!groupExists || theGroup.numUnread == 0) goto exit1;
  704.     }
  705.     
  706.     /* Get the subjects and authors from the server. */
  707.     
  708.     err = GetSubjectsAndAuthorsFromNet(groupName, &theGroup, groupKind, maxFetch,
  709.         &subjectArray, &numSubjects, &subjectStrings, &firstFetched);
  710.     if (err != noErr) goto exit2;
  711.     if (numSubjects == 0) goto exit1;
  712.     
  713.     /* Create the subject window. */
  714.     
  715.     c2pstr(groupName);
  716.     err = MakeNewWindow((StringPtr)groupName, &wind);
  717.     if (err != noErr) goto exit2;
  718.     SetPort(wind);
  719.     info = (TWindow**)GetWRefCon(wind);
  720.     
  721.     (**info).subjectArray = subjectArray;
  722.     subjectArray = nil;
  723.     (**info).numSubjects = numSubjects;
  724.     (**info).firstFetched = firstFetched;
  725.     (**info).strings = subjectStrings;
  726.     subjectStrings = nil;
  727.     (**info).parentWindow = groupWind;
  728.     (**info).parentGroup = index;
  729.     (**info).groupNameOffset = theGroup.nameOffset;
  730.     
  731.     /* Build the threads. */
  732.  
  733.     err = BuildThreads(wind);
  734.     if (err != noErr) goto exit2;
  735.     
  736.     /* Finish initializing the new subject window. */
  737.  
  738.     err = AddChild(groupWind, wind);
  739.     if (err != noErr) goto exit2;
  740.     SetPt(&childCell, 0, 0);
  741.     MyLSetSelect(true, childCell, (**info).theList);
  742.     if (!(**info).windPosLocked) {
  743.         err = DoZoom(wind, inZoomOut);
  744.         if (err != noErr) goto exit2;
  745.     }
  746.     
  747.     /* For a user group list, update the unread list and the number
  748.        of unread articles counter. */
  749.     
  750.     if (groupKind == kUserGroup) {
  751.         err = UpdateUnreadList(wind);
  752.         if (err != noErr) goto exit2;
  753.         (*groupArray)[index].onlyRedrawCount = true;
  754.         LDraw(theCell, theList);
  755.         (*groupArray)[index].onlyRedrawCount = false;
  756.     }
  757.     
  758.     /* Show the new subject window. */
  759.     
  760.     MyShowWindow(wind);
  761.     
  762.     SetPort(port);
  763.     return noErr;
  764.     
  765. exit1:
  766.  
  767.     MyDisposeHandle(subjectArray);
  768.     MyDisposeHandle(subjectStrings);
  769.     if (groupKind == kUserGroup) {
  770.         DisposeGroupUnreadList(&theGroup);
  771.         (*groupArray)[index] = theGroup;
  772.         (*groupArray)[index].onlyRedrawCount = true;
  773.         LDraw(theCell, theList);
  774.         (*groupArray)[index].onlyRedrawCount = false;
  775.     }
  776.     *hasArts = false;
  777.     SetPort(port);
  778.     return noErr;
  779.     
  780. exit2:
  781.  
  782.     if (wind != nil) {
  783.         /* Hack to avoid marking articles read in mark.c/UpdateUnreadList */
  784.         (**info).firstFetched = 0x7fffffff;
  785.         (**info).numSubjectsInList = 0;
  786.     }
  787.     DoClose(wind);
  788.     MyDisposeHandle(subjectArray);
  789.     MyDisposeHandle(subjectStrings);
  790.     SetPort(port);
  791.     return err;
  792. }
  793.  
  794.  
  795.  
  796. /*----------------------------------------------------------------------------
  797.     OpenSelectedCells 
  798.     
  799.     Open all the selected articles in a subject list window.
  800.     
  801.     Entry:    wind = pointer to subject list window.
  802.     
  803.     Exit:    function result = error code.
  804. ----------------------------------------------------------------------------*/
  805.  
  806. static OSErr OpenSelectedCells (WindowPtr wind)
  807. {
  808.     Cell theCell;
  809.     TWindow **info;
  810.     ListHandle theList;
  811.     short *p, *pBegin, numSelected=0, numOpened=0;
  812.     OSErr err = noErr;
  813.     WindowPtr child;
  814.     
  815.     info = (TWindow**) GetWRefCon(wind);
  816.     theList = (**info).theList;
  817.     
  818.     SetPt(&theCell, 0, (**theList).dataBounds.bottom-1);
  819.     while (theCell.v >= 0) {
  820.         pBegin = (**theList).cellArray;
  821.         p = pBegin + theCell.v;
  822.         while (*p >= 0 && p >= pBegin) p--;
  823.         theCell.v = p - pBegin;
  824.         if (p >= pBegin) {
  825.             err = OpenSubjectCell(wind, theCell, 1, nil, &child);
  826.             if (err != noErr) return err;
  827.             numSelected++;
  828.             if (child != nil) numOpened++;
  829.         }
  830.         theCell.v--;
  831.     }
  832.     if (numOpened < numSelected) {
  833.         if (numSelected == 1) {
  834.             NoteMessageNumber(kStrSelArticleDoesntExist);
  835.         } else {
  836.             if (numOpened == 0) {
  837.                 NoteMessageNumber(kStrNoneExist);
  838.             } else {
  839.                 NoteMessageNumber(kStrSomeDontExist);
  840.             }
  841.         }
  842.     }
  843.     return noErr;
  844. }
  845.  
  846.  
  847.  
  848. /*----------------------------------------------------------------------------
  849.     CancelSubjectCell 
  850.     
  851.     Cancel the article corresponding to a cell in a subject list window.
  852.     
  853.     Entry:    wind = pointer to subject list window.
  854.             cell = cell to cancel.
  855.             statusMsg = status message, C-format.
  856.     
  857.     Exit:    function result = error code.
  858.             *canceled = true if canceled or article does not exist,
  859.                 false if user is not owner of article.
  860. ----------------------------------------------------------------------------*/
  861.  
  862. static OSErr CancelSubjectCell (WindowPtr wind, Cell theCell, char *statusMsg,
  863.     Boolean *canceled)
  864. {
  865.     TWindow **info;
  866.     ListHandle theList;
  867.     TSubject **subjectArray;
  868.     short index, cellDataLen;
  869.     long number;
  870.     CStr255 groupName, idStr;
  871.     OSErr err = noErr;
  872.     Handle text = nil;
  873.     Handle groups = nil;
  874.     long length;
  875.     Boolean attachedFile;
  876.     
  877.     info = (TWindow**)GetWRefCon(wind);
  878.     theList = (**info).theList;
  879.     subjectArray = (**info).subjectArray;
  880.     cellDataLen = 2;
  881.     LGetCell(&index, &cellDataLen, theCell, theList);
  882.     number = (*subjectArray)[index].number;
  883.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  884.     
  885.     err = GetArticle(nil, 0, groupName, number, nil, "HEAD", false, false, &text, 
  886.         &length, &attachedFile);
  887.     if (err != noErr) return err;
  888.     
  889.     if (text == nil) {
  890.         *canceled = true;
  891.         return noErr;
  892.     }
  893.     
  894.     if (!UserIsPoster(text)) {
  895.         *canceled = false;
  896.         return noErr;
  897.     }
  898.     
  899.     if (!FindHeaderCString(text, "Message-ID", idStr, sizeof(idStr))) goto exit1;
  900.     
  901.     err = FindHeaderHandle(text, "Newsgroups", &groups);
  902.     if (err != noErr) goto exit2;
  903.     if (groups == nil) goto exit3;
  904.     
  905.     MyDisposeHandle(text);
  906.     text = nil;
  907.     
  908.     err = CancelArticle(idStr, groups, statusMsg);
  909.     if (err != noErr) goto exit2;
  910.     
  911.     *canceled = true;
  912.     MyDisposeHandle(groups);
  913.     return noErr;
  914.     
  915. exit1:
  916.  
  917.     MyDisposeHandle(text);
  918.     MyDisposeHandle(groups);
  919.     ErrorMessageNumber(kStrNoMsgID);
  920.     return userCanceledErr;
  921.     
  922. exit2:
  923.  
  924.     MyDisposeHandle(text);
  925.     MyDisposeHandle(groups);
  926.     return err;
  927.     
  928. exit3:
  929.  
  930.     MyDisposeHandle(text);
  931.     MyDisposeHandle(groups);
  932.     ErrorMessageNumber(kStrNoNewsgroupsHeader);
  933.     return userCanceledErr;
  934. }
  935.  
  936.  
  937.  
  938. /*----------------------------------------------------------------------------
  939.     EncodedTextBeginLineIsRequired
  940.     
  941.     Determine whether or not an article requires a BinHex or uuencode
  942.     beginning "flag" line for attached binaries.
  943.     
  944.     Entry:    subjectArray = handle to subject array.
  945.             index = index in subject array.
  946.                 
  947.     Exit:    function result = true if flag line required.
  948. ----------------------------------------------------------------------------*/
  949.  
  950. Boolean EncodedTextBeginLineIsRequired (TSubject **subjectArray, short index)
  951. {
  952.     TSubject *s;
  953.     
  954.     s = &(*subjectArray)[index];
  955.     return s->partNum == 0 || s->partNum == 0x7fff ||
  956.         (!s->incomplete && s->threadOrdinal == 1);
  957. }
  958.  
  959.  
  960.  
  961. /*----------------------------------------------------------------------------
  962.     OpenMessageWindowForOneCell 
  963.     
  964.     Open a reply, forward, or redirect window for a cell in a subject list window.
  965.     
  966.     Entry:    wind = pointer to subject list window.
  967.             cell = cell in window.
  968.             modifiers = modifiers field from event record.
  969.             func = pointer to function to open one message window.
  970.     
  971.     Exit:    function result = error code.
  972. ----------------------------------------------------------------------------*/
  973.  
  974. static OSErr OpenMessageWindowForOneCell (WindowPtr wind, Cell theCell,
  975.     short modifiers, OSErr (*func)(Handle, Handle, long, long, Boolean))
  976. {
  977.     TWindow **info;
  978.     ListHandle theList;
  979.     TSubject **subjectArray;
  980.     short index, cellDataLen;
  981.     long number;
  982.     CStr255 groupName;
  983.     OSErr err = noErr;
  984.     Handle text = nil;
  985.     long length;
  986.     Boolean attachedFile, requireEncodedTextBeginLine;
  987.     
  988.     info = (TWindow**)GetWRefCon(wind);
  989.     theList = (**info).theList;
  990.     subjectArray = (**info).subjectArray;
  991.     cellDataLen = 2;
  992.     LGetCell(&index, &cellDataLen, theCell, theList);
  993.     number = (*subjectArray)[index].number;
  994.     requireEncodedTextBeginLine = 
  995.         EncodedTextBeginLineIsRequired(subjectArray, index);
  996.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  997.     
  998.     err = GetArticle(nil, 0, groupName, number, nil, "ARTICLE", true, 
  999.         requireEncodedTextBeginLine, &text, &length, &attachedFile);
  1000.     if (err != noErr) return err;
  1001.     
  1002.     if (text == nil) return noErr;
  1003.  
  1004.      err = (*func)(text, nil, 0, 0, (modifiers & optionKey) != 0);
  1005.  
  1006.     MyDisposeHandle(text);
  1007.     return err;
  1008. }
  1009.  
  1010.  
  1011.  
  1012. /*----------------------------------------------------------------------------
  1013.     OpenMessageWindowForAllSelectedCells 
  1014.     
  1015.     Open a reply, forward, or redirect message window for all selected
  1016.     cells in a subject window.
  1017.             
  1018.     Entry:    wind = pointer to subject window.
  1019.             modifiers = modifiers field from event record.
  1020.             func = pointer to function to open one message window.
  1021.             
  1022.     Exit:    function result = error code.
  1023. ----------------------------------------------------------------------------*/
  1024.  
  1025. static OSErr OpenMessageWindowForAllSelectedCells (WindowPtr wind, 
  1026.     short modifiers, OSErr (*func)(Handle, Handle, long, long, Boolean))
  1027. {
  1028.     Cell theCell;
  1029.     TWindow **info;
  1030.     ListHandle theList;
  1031.     short *p, *pBegin;
  1032.     OSErr err = noErr;
  1033.     
  1034.     info = (TWindow**) GetWRefCon(wind);
  1035.     theList = (**info).theList;
  1036.     
  1037.     SetPt(&theCell, 0, (**theList).dataBounds.bottom-1);
  1038.     while (theCell.v >= 0) {
  1039.         pBegin = (**theList).cellArray;
  1040.         p = pBegin + theCell.v;
  1041.         while (*p >= 0 && p >= pBegin) p--;
  1042.         theCell.v = p - pBegin;
  1043.         if (p >= pBegin) {
  1044.             err = OpenMessageWindowForOneCell(wind, theCell, modifiers, func);
  1045.             if (err != noErr) return err;
  1046.         }
  1047.         theCell.v--;
  1048.     }
  1049.     return noErr;
  1050. }
  1051.  
  1052.  
  1053.  
  1054. /*----------------------------------------------------------------------------
  1055.     FormFileName 
  1056.     
  1057.     Form the default file name for saving articles.
  1058.             
  1059.     Entry:    wind = pointer to subject window.
  1060.             theCell = cell in window.
  1061.     
  1062.     Exit:    function result = error code.
  1063.             fileName = default file name.
  1064.             
  1065.     The file name is formed from the subject of the first selected cell
  1066.     which is >= theCell.
  1067. ----------------------------------------------------------------------------*/
  1068.  
  1069. static OSErr FormFileName (WindowPtr wind, Cell theCell, Str31 fileName)
  1070. {
  1071.     TWindow **info;
  1072.     TSubject **subjectArray;
  1073.     ListHandle theList;
  1074.     Str255 subject;
  1075.     long subjectOffset;
  1076.     short cellDataLen, index;
  1077.     Handle strings;
  1078.     
  1079.     info = (TWindow**)GetWRefCon(wind);
  1080.     subjectArray = (**info).subjectArray;
  1081.     theList = (**info).theList;
  1082.     strings = (**info).strings;
  1083.     if (!LGetSelect(true, &theCell, theList)) return userCanceledErr;
  1084.     cellDataLen = 2;
  1085.     LGetCell(&index, &cellDataLen, theCell, theList);
  1086.     subjectOffset = (*subjectArray)[index].subjectOffset;
  1087.     strcpy((char*)subject, *strings + subjectOffset);
  1088.     c2pstr((char*)subject);
  1089.     MakeLegalFileName(subject, fileName);
  1090.     return noErr;
  1091. }
  1092.  
  1093.  
  1094.  
  1095. /*----------------------------------------------------------------------------
  1096.     SaveOneCellToFile 
  1097.     
  1098.     Save a single cell to a file.
  1099.             
  1100.     Entry:    wind = pointer to subject window.
  1101.             theCell = cell to save.
  1102.             refNum = file ref num.
  1103.             statusMsg = status message.
  1104.             *artNum = article ordinal within sequence of articles
  1105.                 being saved (1,2,3,...).
  1106.             numArticlestoSave = total number of articles being saved.
  1107.             writeSeparator = true to write separator before first saved
  1108.                 article.
  1109.             saveEncodedText = true to save encoded text.
  1110.     
  1111.     Exit:    function result = error code.
  1112.             *artNum updated.
  1113. ----------------------------------------------------------------------------*/
  1114.  
  1115. static OSErr SaveOneCellToFile (WindowPtr wind, Cell theCell, short refNum, 
  1116.     char *statusMsg, short *artNum, short numArticlesToSave, 
  1117.     Boolean writeSeparator, Boolean saveEncodedText)
  1118. {
  1119.     TWindow **info;
  1120.     ListHandle theList;
  1121.     TSubject **subjectArray;
  1122.     short index, cellDataLen;
  1123.     long number;
  1124.     CStr255 groupName, msg;
  1125.     OSErr err = noErr;
  1126.     TAttachedFileKind fileKind;
  1127.     char *separator;
  1128.     long length;
  1129.     Boolean truncateIfAttachedFile, requireEncodedTextBeginLine;
  1130.     short numMarked;
  1131.     
  1132.     info = (TWindow**)GetWRefCon(wind);
  1133.     theList = (**info).theList;
  1134.     subjectArray = (**info).subjectArray;
  1135.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  1136.     cellDataLen = 2;
  1137.     LGetCell(&index, &cellDataLen, theCell, theList);
  1138.     
  1139.     while (true) {
  1140.         if (writeSeparator) {
  1141.             separator = 
  1142.                 "\r----------------------------------------------------------------------\r\r";
  1143.             length = strlen(separator);
  1144.             err = MyFSWriteNoCache(refNum, &length, separator, nil);
  1145.             if (err != noErr) return err;
  1146.         }
  1147.         writeSeparator = true;
  1148.         (*artNum)++;
  1149.         if (numArticlesToSave == 1) {
  1150.             strcpy(msg, statusMsg);
  1151.         } else {
  1152.             sprintf(msg, statusMsg, *artNum, numArticlesToSave);
  1153.         }
  1154.         err = DisplayStatusMessage(msg);
  1155.         if (err != noErr) return err;
  1156.         number = (*subjectArray)[index].number;
  1157.         truncateIfAttachedFile = !saveEncodedText;
  1158.         requireEncodedTextBeginLine = 
  1159.             EncodedTextBeginLineIsRequired(subjectArray, index);
  1160.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum, 
  1161.             truncateIfAttachedFile, requireEncodedTextBeginLine, &fileKind);
  1162.         if (err != noErr) return err;
  1163.         if ((*subjectArray)[index].collapsed) {
  1164.             index = (*subjectArray)[index].nextInThread;
  1165.             if (index == -1) break;
  1166.         } else {
  1167.             break;
  1168.         }
  1169.     }
  1170.     MarkSubjectCell(wind, theCell, true, &numMarked);
  1171.     
  1172.     return noErr;
  1173. }
  1174.  
  1175.  
  1176.  
  1177. /*----------------------------------------------------------------------------
  1178.     SaveSelectedArticlesToFile 
  1179.     
  1180.     Save selected articles to a file.
  1181.             
  1182.     Entry:    wind = pointer to subject window.
  1183.             fSpec = pointer to file spec.
  1184.             scriptTag = script code.
  1185.             append = true to append to end of file.
  1186.             saveEncodedText = true to save encoded text.
  1187.             saveThreadsToSeparateFiles = true to save threads to
  1188.                 separate files.
  1189.             firstFileAlreadyCreated = true if the first file to which
  1190.                 the articles should be saved has already been created.
  1191.     
  1192.     Exit:    function result = error code.
  1193. ----------------------------------------------------------------------------*/
  1194.  
  1195. static OSErr SaveSelectedArticlesToFile (WindowPtr wind, FSSpec *fSpec, 
  1196.     ScriptCode scriptTag, Boolean append, Boolean saveEncodedText,
  1197.     Boolean saveThreadsToSeparateFiles, Boolean firstFileAlreadyCreated)
  1198. {
  1199.     TWindow **info;
  1200.     ListHandle theList;
  1201.     TSubject **subjectArray;
  1202.     short cellDataLen, index;
  1203.     short numCells;
  1204.     Cell theCell;
  1205.     short *p, *pBegin, *pEnd, numArticlesToSave = 0, artNum = 0;
  1206.     OSErr err = noErr;
  1207.     CStr255 statusMsg;
  1208.     short refNum = 0;
  1209.     Boolean empty, writeSeparator;
  1210.     short threadHeadIndex, prevThreadHeadIndex;
  1211.     
  1212.     MyICReadSharedPrefs(kICeditorHelper);
  1213.     
  1214.     info = (TWindow**) GetWRefCon(wind);
  1215.     theList = (**info).theList;
  1216.     subjectArray = (**info).subjectArray;
  1217.     numCells = (**theList).dataBounds.bottom;
  1218.     
  1219.     SetPt(&theCell, 0, 0);
  1220.     while (theCell.v < numCells) {
  1221.         pBegin = (**theList).cellArray;
  1222.         pEnd = pBegin + numCells;
  1223.         p = pBegin + theCell.v;
  1224.         while (*p >= 0 && p < pEnd) p++;
  1225.         theCell.v = p - pBegin;
  1226.         if (p < pEnd) {
  1227.             cellDataLen = 2;
  1228.             LGetCell(&index, &cellDataLen, theCell, theList);
  1229.             if ((*subjectArray)[index].collapsed) {
  1230.                 numArticlesToSave += (*subjectArray)[index].threadLength;
  1231.             } else {
  1232.                 numArticlesToSave++;
  1233.             }
  1234.         }
  1235.         theCell.v++;
  1236.     }
  1237.     
  1238.     if (numArticlesToSave == 0) {
  1239.         return noErr;
  1240.     } else if (numArticlesToSave == 1) {
  1241.         GetCString(kStrGettingAndSavingArticle, statusMsg);
  1242.     } else {
  1243.         GetCString(kStrGettingAndSavingArticleNofM, statusMsg);
  1244.     }
  1245.     
  1246.     if (!saveThreadsToSeparateFiles) {
  1247.         err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  1248.             scriptTag, append, &refNum, &empty);
  1249.         if (err != noErr) return err;
  1250.         writeSeparator = !empty;
  1251.     }
  1252.     
  1253.     SetPt(&theCell, 0, 0);
  1254.     prevThreadHeadIndex = -1;
  1255.     while (theCell.v < numCells) {
  1256.         pBegin = (**theList).cellArray;
  1257.         pEnd = pBegin + numCells;
  1258.         p = pBegin + theCell.v;
  1259.         while (*p >= 0 && p < pEnd) p++;
  1260.         theCell.v = p - pBegin;
  1261.         if (p < pEnd) {
  1262.             if (saveThreadsToSeparateFiles) {
  1263.                 cellDataLen = 2;
  1264.                 LGetCell(&index, &cellDataLen, theCell, theList);
  1265.                 threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  1266.                 if (threadHeadIndex != prevThreadHeadIndex) {
  1267.                     if (refNum != 0) {
  1268.                         MyFSClose(refNum, GiveTime);
  1269.                         refNum = 0;
  1270.                         err = FormFileName(wind, theCell, fSpec->name);
  1271.                         if (err != noErr) goto exit;
  1272.                     }
  1273.                     if (prevThreadHeadIndex != -1 || !firstFileAlreadyCreated) {
  1274.                         err = FileOrFolderExists(fSpec);
  1275.                         if (err != noErr && err != fnfErr) goto exit;
  1276.                         if (err == noErr && !append) {
  1277.                             err = MakeFileNameUnique(fSpec, nil);
  1278.                             if (err != noErr) goto exit;
  1279.                         }
  1280.                     }
  1281.                     err = OpenDataForkWriteCreateIfMissing(fSpec, 
  1282.                         gPrefs.savedArtCreator, 'TEXT',
  1283.                         scriptTag, append, &refNum, &empty);
  1284.                     if (err != noErr) goto exit;
  1285.                     writeSeparator = !empty;
  1286.                     prevThreadHeadIndex = threadHeadIndex;
  1287.                 }
  1288.             }
  1289.             err = SaveOneCellToFile(wind, theCell, refNum, statusMsg, &artNum, 
  1290.                 numArticlesToSave, writeSeparator, saveEncodedText);
  1291.             if (err != noErr) goto exit;
  1292.             writeSeparator = true;
  1293.         }
  1294.         theCell.v++;
  1295.     }
  1296.     
  1297.     DoMarkCommand(wind, true);
  1298.  
  1299. exit:
  1300.  
  1301.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  1302.     return err;
  1303. }
  1304.  
  1305.  
  1306.  
  1307. /*----------------------------------------------------------------------------
  1308.     PrintOneArticle 
  1309.     
  1310.     Print a single article.
  1311.             
  1312.     Entry:    groupName = group name.
  1313.             number = article number.
  1314.             artNum = pointer to article ordinal within sequence of articles
  1315.                 being printed (1,2,3,...)
  1316.             numArts = number of articles being printed.
  1317.             requireEncodedTextBeginLIne = true if BinHex or uuencode text
  1318.                 must include special "begin" flag line.
  1319.     
  1320.     Exit:    function result = error code.
  1321. ----------------------------------------------------------------------------*/
  1322.  
  1323. static OSErr PrintOneArticle (CStr255 groupName, long number,
  1324.     short *artNum, short numArts, Boolean requireEncodedTextBeginLine)
  1325. {
  1326.     OSErr err = noErr;
  1327.     Handle text = nil;
  1328.     long textLength;
  1329.     Boolean attachedFile;
  1330.     CStr255 subject, from, str;
  1331.     CStr255 msg, fmt;
  1332.  
  1333.     if (numArts == 1) {
  1334.         GetCString(kStrGettingAndPrinting, msg);
  1335.     } else {
  1336.         GetCString(kStrGettingAndPrintingNofM, fmt);
  1337.         sprintf(msg, fmt, *artNum, numArts);
  1338.     }
  1339.     err = DisplayStatusMessage(msg);
  1340.     if (err != noErr) return err;
  1341.     err = GetArticle(nil, 0, groupName, number, nil, "ARTICLE", true, 
  1342.         requireEncodedTextBeginLine, &text, &textLength, &attachedFile);
  1343.     if (err != noErr) return err;
  1344.     FindHeaderCString(text, "Subject", subject, sizeof(subject));
  1345.     if (FindHeaderCString(text, "From", from, sizeof(from))) {
  1346.         FormatAuthorName(from);
  1347.         sprintf(str, "%.40s, %.100s", from, subject);
  1348.     } else {
  1349.         strcpy(str, subject);
  1350.     }
  1351.     err = PrintText(text, 0, textLength, str);
  1352.     MyDisposeHandle(text);
  1353.     (*artNum)++;
  1354.     return err;
  1355. }
  1356.  
  1357.  
  1358.  
  1359. /*----------------------------------------------------------------------------
  1360.     PrintOneCell 
  1361.     
  1362.     Print a single cell.
  1363.             
  1364.     Entry:    wind = pointer to subject window.
  1365.             theCell = cell to print.
  1366.             artNum = pointer to article ordinal within sequence of articles
  1367.                 being printed (1,2,3,...)
  1368.             numArts = number of articles being printed.
  1369.     
  1370.     Exit:    function result = error code.
  1371. ----------------------------------------------------------------------------*/
  1372.  
  1373. static OSErr PrintOneCell (WindowPtr wind, Cell theCell, 
  1374.     short *artNum, short numArts)
  1375. {
  1376.     TWindow **info;
  1377.     ListHandle theList;
  1378.     TSubject **subjectArray;
  1379.     short index, cellDataLen;
  1380.     long number;
  1381.     CStr255 groupName;
  1382.     OSErr err = noErr;
  1383.     Boolean requireEncodedTextBeginLine;
  1384.     short numMarked;
  1385.     
  1386.     info = (TWindow**)GetWRefCon(wind);
  1387.     theList = (**info).theList;
  1388.     subjectArray = (**info).subjectArray;
  1389.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  1390.     cellDataLen = 2;
  1391.     LGetCell(&index, &cellDataLen, theCell, theList);
  1392.     
  1393.     while (true) {
  1394.         number = (*subjectArray)[index].number;
  1395.         requireEncodedTextBeginLine = 
  1396.             EncodedTextBeginLineIsRequired(subjectArray, index);
  1397.         err = PrintOneArticle(groupName, number, artNum, numArts, 
  1398.             requireEncodedTextBeginLine);
  1399.         if (err != noErr) return err;
  1400.         if ((*subjectArray)[index].collapsed) {
  1401.             index = (*subjectArray)[index].nextInThread;
  1402.             if (index == -1) break;
  1403.         } else {
  1404.             break;
  1405.         }
  1406.     }
  1407.     
  1408.     MarkSubjectCell(wind, theCell, true, &numMarked);
  1409.     
  1410.     return noErr;
  1411. }
  1412.  
  1413.  
  1414.  
  1415. /*----------------------------------------------------------------------------
  1416.     DragSubjectsSendProc 
  1417.     
  1418.     The Drag Manager send proc for dragging subjects to the Finder.
  1419.             
  1420.     Entry:    theType = flavor type = 'SPEC'.
  1421.             dragSendRefCon = nil.
  1422.             theItemRef = item reference.
  1423.             theDragRef = drag reference.
  1424.     
  1425.     Exit:    function result = error code.
  1426.             gDragTheFile = file spec to be used to save the articles.
  1427. ----------------------------------------------------------------------------*/
  1428.  
  1429. static pascal OSErr DragSubjectsSendProc (FlavorType theType,
  1430.     void *dragSendRefCon, ItemReference theItemRef, DragReference theDragRef)
  1431. {
  1432.     AEDesc dropLocation;
  1433.     OSErr err = noErr;
  1434.     Cell theCell = {0, 0};
  1435.     
  1436.     if (DragTargetWasTrash(theDragRef)) return userCanceledErr;
  1437.     
  1438.     MyICReadSharedPrefs(kICeditorHelper);
  1439.  
  1440.     err = GetDropLocation(theDragRef, &dropLocation);
  1441.     if (err != noErr) return err;
  1442.     
  1443.     err = GetDropLocationDirectory(&dropLocation, &gDragTheFile.vRefNum, 
  1444.         &gDragTheFile.parID);
  1445.     if (err != noErr) goto exit;
  1446.     
  1447.     err = FormFileName(gDragSrcWindow, theCell, gDragTheFile.name);
  1448.     if (err != noErr) goto exit;
  1449.     
  1450.     err = MakeFileNameUnique(&gDragTheFile, nil);
  1451.     if (err != noErr) goto exit;
  1452.  
  1453.     err = FSpCreate(&gDragTheFile, gPrefs.savedArtCreator, 'TEXT', smSystemScript);
  1454.     if (err != noErr) goto exit;
  1455.     
  1456.     err = SetDragItemFlavorData(theDragRef, theItemRef, 'SPEC', 
  1457.         &gDragTheFile, sizeof(FSSpec), 0);
  1458.  
  1459. exit:
  1460.  
  1461.     AEDisposeDesc(&dropLocation);
  1462.     return err;
  1463. }
  1464.  
  1465.  
  1466.  
  1467. /*----------------------------------------------------------------------------
  1468.     SubjectListClickLoop
  1469.     
  1470.     The click loop routine for subject lists when we have the Drag Manager.
  1471.     
  1472.     Exit:    function result = true if mouse button still down, false
  1473.                 if mouse button released.
  1474. ----------------------------------------------------------------------------*/
  1475.  
  1476. static pascal SubjectListClickLoop (void)
  1477. {    
  1478.     TWindow **info;
  1479.     ListHandle theList;
  1480.     OSErr err = noErr;
  1481.     DragReference dragRef;
  1482.     Boolean haveDragRef = false;
  1483.     RgnHandle dragRgn = nil;
  1484.     PromiseHFSFlavor promise;
  1485.     
  1486.     info = (TWindow**)GetWRefCon(gDragSrcWindow);
  1487.     theList = (**info).theList;
  1488.  
  1489.     if (gFirstListClickCall) {
  1490.         gFirstListClickCall = false;
  1491.         return true;
  1492.     }
  1493.     
  1494.     if (WaitMouseMoved(gCurEvent.where)) {
  1495.         err = NewDrag(&dragRef);
  1496.         if (err != noErr) goto exit;
  1497.         MyICReadSharedPrefs(kICeditorHelper);
  1498.         haveDragRef = true;
  1499.         promise.fileType = 'TEXT';
  1500.         promise.fileCreator = gPrefs.savedArtCreator;
  1501.         promise.fdFlags = 0;
  1502.         promise.promisedFlavor = 'SPEC';
  1503.         err = AddDragItemFlavor(dragRef, 1, flavorTypePromiseHFS, &promise, 
  1504.             sizeof(PromiseHFSFlavor), 0);
  1505.         if (err != noErr) goto exit;
  1506.         err = AddDragItemFlavor(dragRef, 1, 'SPEC', nil, 0, 0);
  1507.         if (err != noErr) goto exit;
  1508.         err = SetDragSendProc(dragRef, gDragSubjectsSendProcUPP, nil);
  1509.         if (err != noErr) goto exit;
  1510.         BuildListSelectedCellsDragRegion(theList, &dragRgn);
  1511.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  1512.         if (err != noErr) goto exit;
  1513.         DisposeRgn(dragRgn);
  1514.         DisposeDrag(dragRef);
  1515.         gDraggedToFinder = true;
  1516.     }
  1517.     
  1518.     return false;
  1519.     
  1520. exit:
  1521.  
  1522.     if (dragRgn != nil) DisposeRgn(dragRgn);
  1523.     if (haveDragRef) DisposeDrag(dragRef);
  1524.     gClickLoopErr = err;
  1525.     return false;
  1526. }
  1527.  
  1528.  
  1529.  
  1530. /*----------------------------------------------------------------------------
  1531.     ExtractBinariesManuallyDlgLabelUserItem
  1532.     
  1533.     A user item procedure to draw the label in the extract binaries
  1534.     manually dialog.
  1535.     
  1536.     Entry:    dlg = pointer to dialog.
  1537.             item = item number.
  1538. ----------------------------------------------------------------------------*/
  1539.  
  1540. static pascal void ExtractBinariesManuallyDlgLabelUserItem (DialogPtr dlg, short item)
  1541. {
  1542.     Str255 label;
  1543.     Handle itemHandle;
  1544.     short itemType;
  1545.     Rect box;
  1546.     TextStyle savedStyle;
  1547.  
  1548.     GetDialogItem(dlg, item, &itemType, &itemHandle, &box);
  1549.     GetPString(kStrExtractBinariesManuallyDlgLabel, label);
  1550.     GetPortTextStyle(&savedStyle);
  1551.     TextFont(systemFont);
  1552.     TextSize(12);
  1553.     TETextBox(label+1, label[0], &box, teForceLeft);
  1554.     SetPortTextStyle(&savedStyle);
  1555. }
  1556.  
  1557.  
  1558.  
  1559. /*----------------------------------------------------------------------------
  1560.     ExtractBinariesManuallyDlgListUserItem
  1561.     
  1562.     A user item procedure to draw the list in the extract binaries
  1563.     manually dialog.
  1564.     
  1565.     Entry:    dlg = pointer to dialog.
  1566.             item = item number.
  1567. ----------------------------------------------------------------------------*/
  1568.  
  1569. static pascal void ExtractBinariesManuallyDlgListUserItem (DialogPtr dlg, short item)
  1570. {
  1571.     Handle itemHandle;
  1572.     short itemType;
  1573.     Rect box;
  1574.  
  1575.     GetDialogItem(dlg, item, &itemType, &itemHandle, &box);
  1576.     FrameRect(&box);
  1577.     LUpdate(dlg->visRgn, gExtractBinariesManuallyList);
  1578. }
  1579.  
  1580.  
  1581.  
  1582. /*----------------------------------------------------------------------------
  1583.     ExtractBinariesManuallyHandleTracking 
  1584.     
  1585.     Drag Manager tracking handler for the extract binaries manually
  1586.     dialog.
  1587.     
  1588.     Entry:    message = tracking message from Drag Manager.
  1589.             wind = pointer to dialog.
  1590.             handlerRefCon = reference constant (nil).
  1591.             theDrag = drag reference.
  1592.             
  1593.     Exit:    function result = error code.
  1594. ----------------------------------------------------------------------------*/
  1595.  
  1596. static pascal OSErr ExtractBinariesManuallyHandleTracking (DragTrackingMessage message,
  1597.     WindowPtr wind, void *handlerRefCon, DragReference theDrag)
  1598. {
  1599.     Rect rView, visible, dataBounds, contentRect;
  1600.     Point where;
  1601.     Boolean inContentRect;
  1602.     short scrollDelta, newDestRow;
  1603.     static short prevScrollDelta;
  1604.     static long scrollTickCount;
  1605.     
  1606.     rView = (**gExtractBinariesManuallyList).rView;
  1607.     visible = (**gExtractBinariesManuallyList).visible;
  1608.     dataBounds = (**gExtractBinariesManuallyList).dataBounds;
  1609.     contentRect = rView;
  1610.     contentRect.top = 0;
  1611.     contentRect.bottom = wind->portRect.bottom;
  1612.  
  1613.     switch (message) {
  1614.     
  1615.         case dragTrackingEnterHandler:
  1616.         
  1617.             break;
  1618.             
  1619.         case dragTrackingEnterWindow:
  1620.         
  1621.             gDragDestRow = -1;
  1622.             prevScrollDelta = 0;
  1623.             break;
  1624.             
  1625.         case dragTrackingInWindow:
  1626.         
  1627.             GetDragMouse(theDrag, &where, nil);
  1628.             GlobalToLocal(&where);
  1629.             inContentRect = PtInRect(where, &contentRect);
  1630.             if (inContentRect) {
  1631.                 scrollDelta = 0;
  1632.                 if (gDragDestRow >= 0) {
  1633.                     if (where.v < rView.top && visible.top > 0) {
  1634.                         scrollDelta = -1;
  1635.                     } else if (where.v > rView.bottom && 
  1636.                         visible.bottom < dataBounds.bottom) 
  1637.                     {
  1638.                         scrollDelta = +1;
  1639.                     }
  1640.                 }
  1641.                 if (scrollDelta == prevScrollDelta) {
  1642.                     if (scrollDelta != 0 && TickCount() - scrollTickCount < 10) 
  1643.                         scrollDelta = 0;
  1644.                 } else {
  1645.                     prevScrollDelta = scrollDelta;
  1646.                     scrollDelta = 0;
  1647.                     scrollTickCount = TickCount();
  1648.                 } 
  1649.                 if (scrollDelta != 0) {
  1650.                     DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1651.                     gDragDestRow = -1;
  1652.                     MyLScroll(scrollDelta, gExtractBinariesManuallyList);
  1653.                 }
  1654.                 newDestRow = ListDestinationRow(gExtractBinariesManuallyList, where);
  1655.                 if (newDestRow != gDragDestRow) {
  1656.                     if (gDragDestRow >= 0) 
  1657.                         DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1658.                     gDragDestRow = newDestRow;
  1659.                     DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1660.                 }
  1661.             } else {
  1662.                 if (gDragDestRow >= 0) {
  1663.                     DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1664.                     gDragDestRow = -1;
  1665.                 }
  1666.                 prevScrollDelta = 0;
  1667.             }
  1668.             break;
  1669.             
  1670.         case dragTrackingLeaveWindow:
  1671.         
  1672.             if (gDragDestRow >= 0) 
  1673.                 DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1674.             gDragDestRow = -1;
  1675.             prevScrollDelta = 0;
  1676.             break;
  1677.     
  1678.         case dragTrackingLeaveHandler:
  1679.         
  1680.             break;
  1681.             
  1682.     }
  1683.  
  1684.     return noErr;
  1685. }
  1686.  
  1687.  
  1688.  
  1689. /*----------------------------------------------------------------------------
  1690.     ExtractBinariesManuallyHandleReceive 
  1691.     
  1692.     Drag Manager receive handler for the extract binaries manually
  1693.     dialog.
  1694.     
  1695.     Entry:    wind = pointer to dialog.
  1696.             handlerRefCon = reference constant (nil).
  1697.             theDrag = drag reference.
  1698.             
  1699.     Exit:    function result = error code.
  1700.     
  1701.     Note: No user interaction or network transactions are permitted in
  1702.     this function.
  1703. ----------------------------------------------------------------------------*/
  1704.  
  1705. static pascal OSErr ExtractBinariesManuallyHandleReceive (WindowPtr wind, 
  1706.     void *handlerRefCon, DragReference theDrag)
  1707. {
  1708.     Boolean changed;
  1709.  
  1710.     if (gDragDestRow == -1) return dragNotAcceptedErr;
  1711.     MoveSelectedListCells(gExtractBinariesManuallyList, gDragDestRow, &changed);
  1712.     return noErr;
  1713. }
  1714.  
  1715.  
  1716.  
  1717. /*----------------------------------------------------------------------------
  1718.     ExtractBinariesManuallyClickLoop
  1719.     
  1720.     The click loop routine for the extract binaries manually dialog list 
  1721.     when we have the Drag Manager. It initiates subject drags.
  1722.     
  1723.     Exit:    function result = true if mouse button still down, false
  1724.                 if mouse button released.
  1725. ----------------------------------------------------------------------------*/
  1726.  
  1727. static Boolean ExtractBinariesManuallyClickLoop (void)
  1728. {    
  1729.     OSErr err = noErr;
  1730.     DragReference dragRef;
  1731.     Boolean haveDragRef = false;
  1732.     RgnHandle dragRgn = nil;
  1733.  
  1734.     if (gFirstListClickCall) {
  1735.         gFirstListClickCall = false;
  1736.         return true;
  1737.     }
  1738.     
  1739.     if (WaitMouseMoved(gCurEvent.where)) {
  1740.         err = NewDrag(&dragRef);
  1741.         if (err != noErr) goto exit;
  1742.         haveDragRef = true;
  1743.         err = AddDragItemFlavor(dragRef, 1, kNewsWatcherSignature, 
  1744.             nil, 0, flavorSenderOnly);
  1745.         if (err != noErr) goto exit;
  1746.         BuildListSelectedCellsDragRegion(gExtractBinariesManuallyList, &dragRgn);
  1747.         gDragDestRow = -1;
  1748.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  1749.         if (err != noErr) goto exit;
  1750.         DisposeRgn(dragRgn);
  1751.         DisposeDrag(dragRef);
  1752.     }
  1753.     
  1754.     return false;
  1755.     
  1756. exit:
  1757.  
  1758.     if (dragRgn != nil) DisposeRgn(dragRgn);
  1759.     if (haveDragRef) DisposeDrag(dragRef);
  1760.     gClickLoopErr = err;
  1761.     return false;
  1762. }
  1763.  
  1764.  
  1765.  
  1766. /*----------------------------------------------------------------------------
  1767.     ExtractBinariesManuallyDialogFilter 
  1768.     
  1769.     Extract binaries manually dialog filter.
  1770.         
  1771.     Entry:    dlg = pointer to dialog.
  1772.             ev = pointer to event record.
  1773.             
  1774.     Exit:    function result = true if event handled and item hit.
  1775.             *itemHit = item number of item hit.
  1776.             ev = pointer to possibly modified event record.
  1777. ----------------------------------------------------------------------------*/
  1778.  
  1779. static pascal Boolean ExtractBinariesManuallyDialogFilter (DialogPtr dlg, 
  1780.     EventRecord *ev, short *itemHit)
  1781. {
  1782.     short itemType;
  1783.     Handle itemHandle;
  1784.     Rect box;
  1785.     Point where;
  1786.     Boolean shift, command;
  1787.  
  1788.     if (ev->what == mouseDown) {
  1789.         GetDialogItem(dlg, kExtractBinariesManuallyListItem, &itemType, &itemHandle, &box);
  1790.         where = ev->where;
  1791.         GlobalToLocal(&where);
  1792.         if (PtInRect(where, &box)) {
  1793.             shift = (ev->modifiers & shiftKey) != 0;
  1794.             command = (ev->modifiers & cmdKey) != 0;
  1795.             if (!shift && !command) {
  1796.                 if (gHaveDragMgr) {
  1797.                     gFirstListClickCall = true;
  1798.                     gClickLoopErr = noErr;
  1799.                     gDragSrcWindow = dlg;
  1800.                     gCurEvent = *ev;
  1801.                     SetListClickLoop(gExtractBinariesManuallyList, 
  1802.                         gExtractBinariesManuallyClickLoopUPP);
  1803.                     LClick(where, ev->modifiers, gExtractBinariesManuallyList);
  1804.                 } else {
  1805.                     OldBeginListClick(gExtractBinariesManuallyList);
  1806.                     SetListClickLoop(gExtractBinariesManuallyList, 
  1807.                         gOldListClickLoopUPP);
  1808.                     LClick(where, ev->modifiers, gExtractBinariesManuallyList);
  1809.                     OldEndListClick();
  1810.                 }
  1811.             } else {
  1812.                 (**gExtractBinariesManuallyList).lClickLoop = nil;
  1813.                 LClick(where, ev->modifiers, gExtractBinariesManuallyList);
  1814.             }
  1815.             return false;
  1816.         }
  1817.     }
  1818.     return DialogFilter(dlg, ev, itemHit);
  1819. }
  1820.  
  1821.  
  1822.  
  1823. /*----------------------------------------------------------------------------
  1824.     Find 
  1825.     
  1826.     Search a subject window for a pattern.
  1827.             
  1828.     Entry:    wind = pointer to subject window.
  1829.             theCell = first cell to search.
  1830.             again = true if "Find Again", in which case the search starts
  1831.                 at the article *following* the first cell to search.
  1832.             gFindPattern = pattern.
  1833.     
  1834.     Exit:    function result = error code.
  1835. ----------------------------------------------------------------------------*/
  1836.  
  1837. static OSErr Find (WindowPtr wind, Cell theCell, Boolean again)
  1838. {
  1839.     TWindow **info;
  1840.     TSubject **subjectArray;
  1841.     TSubject theSubject;
  1842.     ListHandle theList;
  1843.     Handle strings;
  1844.     short index, cellDataLen, numCells, autoExpandedThread;
  1845.     unsigned long tickLongTime;
  1846.     Boolean longTime = false;
  1847.     OSErr err = noErr;
  1848.     char state;
  1849.     CStr255 str;
  1850.     Boolean authorMatch, subjectMatch;
  1851.     Cell autoExpandedCell;
  1852.     
  1853.     info = (TWindow**)GetWRefCon(wind);
  1854.     subjectArray = (**info).subjectArray;
  1855.     theList = (**info).theList;
  1856.     strings = (**info).strings;
  1857.     numCells = (**theList).dataBounds.bottom;
  1858.     tickLongTime = TickCount() + 30;
  1859.     
  1860.     state = MyHGetState(strings);
  1861.     MyHLock(strings);
  1862.     
  1863.     while (theCell.v < numCells) {
  1864.     
  1865.         cellDataLen = 2;
  1866.         LGetCell(&index, &cellDataLen, theCell, theList);
  1867.         theSubject = (*subjectArray)[index];
  1868.         
  1869.         if (again) {
  1870.             if (theSubject.collapsed) {
  1871.                 index = theSubject.nextInThread;
  1872.             } else {
  1873.                 index = -1;
  1874.             }
  1875.             again = false;
  1876.         }
  1877.         
  1878.         while (index != -1) {
  1879.         
  1880.             theSubject = (*subjectArray)[index];
  1881.             
  1882.             if (theSubject.authorOffset == -1) {
  1883.                 authorMatch = false;
  1884.             } else {
  1885.                 strcpy(str, *strings + theSubject.authorOffset);
  1886.                 FormatAuthorName(str);
  1887.                 authorMatch = MyIsASubstring(str, gFindPattern);
  1888.             }
  1889.             
  1890.             subjectMatch = theSubject.threadOrdinal == 1 &&
  1891.                 MyIsASubstring(*strings + theSubject.subjectOffset, gFindPattern);
  1892.             
  1893.             if (authorMatch || subjectMatch) {
  1894.                 MyHSetState(strings, state);
  1895.                 autoExpandedThread = (**info).autoExpandedThread;
  1896.                 if (autoExpandedThread != -1 && 
  1897.                     autoExpandedThread != theSubject.threadHeadIndex &&
  1898.                     !(*subjectArray)[autoExpandedThread].collapsed &&
  1899.                     FindParentCell(wind, autoExpandedThread, &autoExpandedCell))
  1900.                 {
  1901.                     ExpandCollapseThread(wind, autoExpandedCell);
  1902.                     (**info).autoExpandedThread = -1;
  1903.                     if (autoExpandedCell.v < theCell.v)
  1904.                         theCell.v -= (*subjectArray)[autoExpandedThread].threadLength - 1;
  1905.                     err = RezoomSubjectWindow(wind, false);
  1906.                     if (err != noErr) return err;
  1907.                 }
  1908.                 if (theSubject.collapsed && theSubject.threadOrdinal > 1) {
  1909.                     ExpandCollapseThread(wind, theCell);
  1910.                     (**info).autoExpandedThread = theSubject.threadHeadIndex;
  1911.                     theCell.v += theSubject.threadOrdinal - 1;
  1912.                     err = RezoomSubjectWindow(wind, true);
  1913.                     if (err != noErr) return err;
  1914.                 }
  1915.                 SelectSingleListItem(theList, theCell);
  1916.                 MyLScrollCenter(theCell, theList);
  1917.                 return noErr;
  1918.             }
  1919.             
  1920.             if (!theSubject.collapsed) break;
  1921.             index = theSubject.nextInThread;
  1922.         }
  1923.     
  1924.         theCell.v++;
  1925.         if ((theCell.v & 0xf) == 0) {
  1926.             if (!longTime && TickCount() > tickLongTime) longTime = true;
  1927.             if (longTime) {
  1928.                 err = GiveTime(false);
  1929.                 if (err != noErr) {
  1930.                     MyHSetState(strings, state);
  1931.                     return err;
  1932.                 }
  1933.             }
  1934.         }
  1935.     }    
  1936.     
  1937.     MyHSetState(strings, state);
  1938.     SysBeep(0);
  1939.     return noErr;
  1940. }
  1941.  
  1942.  
  1943.  
  1944. /*----------------------------------------------------------------------------
  1945.     DoSave 
  1946.     
  1947.     Handle the "Save" command.
  1948.             
  1949.     Entry:    wind = pointer to subject window.
  1950.             modifiers = modifiers field from event record.
  1951.     
  1952.     Exit:    function result = error code.
  1953. ----------------------------------------------------------------------------*/
  1954.  
  1955. static OSErr DoSave (WindowPtr wind, short modifiers)
  1956. {
  1957.     Str31 fileName;
  1958.     FSSpec fSpec;
  1959.     ScriptCode scriptTag;
  1960.     Boolean append = false;
  1961.     OSErr err = noErr;
  1962.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  1963.     Cell theCell = {0, 0};
  1964.  
  1965.     saveEncodedText = gPrefs.saveEncodedText;
  1966.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  1967.     err = FormFileName(wind, theCell, fileName);
  1968.     if (err != noErr) return err;
  1969.     if (gPrefs.savedArtDefaultFolder && (modifiers & optionKey) == 0) {
  1970.         err = CheckArticleFileExists(fileName, &fSpec, &scriptTag, 
  1971.             &append, &saveEncodedText, &saveThreadsToSeparateFiles);
  1972.         if (err != noErr) return err;
  1973.     } else {
  1974.         err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag, 
  1975.             &saveEncodedText, &saveThreadsToSeparateFiles);
  1976.         if (err != noErr) return err;
  1977.     }
  1978.     return SaveSelectedArticlesToFile(wind, &fSpec, scriptTag, append,
  1979.         saveEncodedText, saveThreadsToSeparateFiles, false);
  1980. }
  1981.  
  1982.  
  1983.  
  1984. /*----------------------------------------------------------------------------
  1985.     DoSaveAs 
  1986.     
  1987.     Handle the "SaveAs" command.
  1988.             
  1989.     Entry:    wind = pointer to subject window.
  1990.     
  1991.     Exit:    function result = error code.
  1992. ----------------------------------------------------------------------------*/
  1993.  
  1994. static OSErr DoSaveAs (WindowPtr wind)
  1995. {
  1996.     Str31 fileName;
  1997.     FSSpec fSpec;
  1998.     ScriptCode scriptTag;
  1999.     OSErr err = noErr;
  2000.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  2001.     Cell theCell = {0, 0};
  2002.  
  2003.     saveEncodedText = gPrefs.saveEncodedText;
  2004.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  2005.     FormFileName(wind, theCell, fileName);
  2006.     err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag, 
  2007.         &saveEncodedText, &saveThreadsToSeparateFiles);
  2008.     if (err != noErr) return err;
  2009.     return SaveSelectedArticlesToFile(wind, &fSpec, scriptTag, false,
  2010.         saveEncodedText, saveThreadsToSeparateFiles, false);
  2011. }
  2012.  
  2013.  
  2014.  
  2015. /*----------------------------------------------------------------------------
  2016.     DoAppend
  2017.     
  2018.     Handle the "Append" command.
  2019.             
  2020.     Entry:    wind = pointer to subject window.
  2021.     
  2022.     Exit:    function result = error code.
  2023. ----------------------------------------------------------------------------*/
  2024.  
  2025. static OSErr DoAppend (WindowPtr wind)
  2026. {
  2027.     FSSpec fSpec;
  2028.     OSErr err = noErr;
  2029.     Boolean saveEncodedText;
  2030.  
  2031.     saveEncodedText = gPrefs.saveEncodedText;
  2032.     err = PresentStandardArticleGetFileDialog(&fSpec, &saveEncodedText);
  2033.     if (err != noErr) return err;
  2034.     return SaveSelectedArticlesToFile(wind, &fSpec, smSystemScript, true,
  2035.         saveEncodedText, false, false);
  2036. }
  2037.  
  2038.  
  2039.  
  2040. /*----------------------------------------------------------------------------
  2041.     DoPrint
  2042.     
  2043.     Handle the "Print" command.
  2044.             
  2045.     Entry:    wind = pointer to subject window.
  2046.     
  2047.     Exit:    function result = error code.
  2048. ----------------------------------------------------------------------------*/
  2049.  
  2050. static OSErr DoPrint (WindowPtr wind)
  2051. {
  2052.     TWindow **info;
  2053.     ListHandle theList;
  2054.     TSubject **subjectArray;
  2055.     short numCells;
  2056.     Cell theCell;
  2057.     short *p, *pBegin, *pEnd;
  2058.     OSErr err = noErr;
  2059.     short artNum, numArts;
  2060.     short cellDataLen, index;
  2061.     
  2062.     err = StartPrint();
  2063.     if (err != noErr) return err;
  2064.     
  2065.     info = (TWindow**) GetWRefCon(wind);
  2066.     theList = (**info).theList;
  2067.     numCells = (**theList).dataBounds.bottom;
  2068.     subjectArray = (**info).subjectArray;
  2069.     
  2070.     SetPt(&theCell, 0, 0);
  2071.     numArts = 0;
  2072.     while (theCell.v < numCells) {
  2073.         pBegin = (**theList).cellArray;
  2074.         pEnd = pBegin + numCells;
  2075.         p = pBegin + theCell.v;
  2076.         while (*p >= 0 && p < pEnd) p++;
  2077.         theCell.v = p - pBegin;
  2078.         if (p < pEnd) {
  2079.             cellDataLen = 2;
  2080.             LGetCell(&index, &cellDataLen, theCell, theList);
  2081.             if ((*subjectArray)[index].collapsed) {
  2082.                 numArts += (*subjectArray)[index].threadLength;
  2083.             } else {
  2084.                 numArts++;
  2085.             }
  2086.         }
  2087.         theCell.v++;
  2088.     }
  2089.     
  2090.     artNum = 1;
  2091.     SetPt(&theCell, 0, 0);
  2092.     while (theCell.v < numCells) {
  2093.         pBegin = (**theList).cellArray;
  2094.         pEnd = pBegin + numCells;
  2095.         p = pBegin + theCell.v;
  2096.         while (*p >= 0 && p < pEnd) p++;
  2097.         theCell.v = p - pBegin;
  2098.         if (p < pEnd) {
  2099.             err = PrintOneCell(wind, theCell, &artNum, numArts);
  2100.             if (err != noErr) goto exit;
  2101.         }
  2102.         theCell.v++;
  2103.     }
  2104.  
  2105. exit:
  2106.  
  2107.     return err;
  2108. }
  2109.  
  2110.  
  2111.  
  2112. /*----------------------------------------------------------------------------
  2113.     DoSelectAll 
  2114.     
  2115.     Handle the "Select All" command for a subject window.
  2116.             
  2117.     Entry:    wind = pointer to subject window.
  2118. ----------------------------------------------------------------------------*/
  2119.  
  2120. static void DoSelectAll (WindowPtr wind)
  2121. {
  2122.     TWindow **info;
  2123.     
  2124.     info = (TWindow**)GetWRefCon(wind);
  2125.     SelectOrDeselectAllListItems((**info).theList, true);
  2126. }
  2127.  
  2128.  
  2129.  
  2130. /*----------------------------------------------------------------------------
  2131.     DoDeselectAll 
  2132.     
  2133.     Handle the "Deselect All" command for a subject window.
  2134.             
  2135.     Entry:    wind = pointer to subject window.
  2136. ----------------------------------------------------------------------------*/
  2137.  
  2138. static void DoDeselectAll (WindowPtr wind)
  2139. {
  2140.     TWindow **info;
  2141.     
  2142.     info = (TWindow**)GetWRefCon(wind);
  2143.     SelectOrDeselectAllListItems((**info).theList, false);
  2144. }
  2145.  
  2146.  
  2147.  
  2148. /*----------------------------------------------------------------------------
  2149.     DoFind 
  2150.     
  2151.     Handle the "Find" command for a subject window.
  2152.             
  2153.     Entry:    wind = pointer to subject window.
  2154.     
  2155.     Exit:    function result = error code.
  2156. ----------------------------------------------------------------------------*/
  2157.  
  2158. static OSErr DoFind (WindowPtr wind)
  2159. {
  2160.     TWindow **info;
  2161.     ListHandle theList;
  2162.     Cell theCell;
  2163.     OSErr err = noErr;
  2164.     
  2165.     err = DoFindDialog();
  2166.     if (err != noErr) return err;
  2167.     info = (TWindow**)GetWRefCon(wind);
  2168.     theList = (**info).theList;
  2169.     SetPt(&theCell, 0, 0);
  2170.     if (!gPrefs.startFindAtBeginning) LGetSelect(true, &theCell, theList);
  2171.     return Find(wind, theCell, false);
  2172. }
  2173.  
  2174.  
  2175.  
  2176. /*----------------------------------------------------------------------------
  2177.     DoFindAgain
  2178.     
  2179.     Handle the "Find Again" command for a subject window.
  2180.             
  2181.     Entry:    wind = pointer to subject window.
  2182.     
  2183.     Exit:    function result = error code.
  2184. ----------------------------------------------------------------------------*/
  2185.  
  2186. static OSErr DoFindAgain (WindowPtr wind)
  2187. {
  2188.     TWindow **info;
  2189.     ListHandle theList;
  2190.     Cell theCell;
  2191.     
  2192.     info = (TWindow**)GetWRefCon(wind);
  2193.     
  2194.     theList = (**info).theList;
  2195.     SetPt(&theCell, 0, 0);
  2196.     LGetSelect(true, &theCell, theList);
  2197.     return Find(wind, theCell, true);
  2198. }
  2199.  
  2200.  
  2201.  
  2202. /*----------------------------------------------------------------------------
  2203.     DoReply 
  2204.     
  2205.     Handle the "Reply" command.
  2206.             
  2207.     Entry:    wind = pointer to subject window.
  2208.             modifiers = modifiers field from event record.
  2209.             
  2210.     Exit:    function result = error code.
  2211. ----------------------------------------------------------------------------*/
  2212.  
  2213. static OSErr DoReply (WindowPtr wind, short modifiers)
  2214. {
  2215.     return OpenMessageWindowForAllSelectedCells(wind, modifiers, OpenReplyWindow);
  2216. }
  2217.  
  2218.  
  2219.  
  2220. /*----------------------------------------------------------------------------
  2221.     DoForward 
  2222.     
  2223.     Handle the "Forward" command.
  2224.             
  2225.     Entry:    wind = pointer to subject window.
  2226.             modifiers = modifiers field from event record.
  2227.             
  2228.     Exit:    function result = error code.
  2229. ----------------------------------------------------------------------------*/
  2230.  
  2231. static OSErr DoForward (WindowPtr wind, short modifiers)
  2232. {
  2233.     return OpenMessageWindowForAllSelectedCells(wind, modifiers, OpenForwardWindow);
  2234. }
  2235.  
  2236.  
  2237.  
  2238. /*----------------------------------------------------------------------------
  2239.     DoRedirect 
  2240.     
  2241.     Handle the "Redirect" command.
  2242.             
  2243.     Entry:    wind = pointer to subject window.
  2244.             modifiers = modifiers field from event record.
  2245.             
  2246.     Exit:    function result = error code.
  2247. ----------------------------------------------------------------------------*/
  2248.  
  2249. static OSErr DoRedirect (WindowPtr wind, short modifiers)
  2250. {
  2251.     return OpenMessageWindowForAllSelectedCells(wind, modifiers, OpenRedirectWindow);
  2252. }
  2253.  
  2254.  
  2255.  
  2256. /*----------------------------------------------------------------------------
  2257.     DoExtractBinaries 
  2258.     
  2259.     Handle the "Extract Binaries" command for a subject window.
  2260.             
  2261.     Entry:    wind = pointer to subject window.
  2262.             modifiers = modifiers field from event record.
  2263.     
  2264.     Exit:    function result = error code.
  2265. ----------------------------------------------------------------------------*/
  2266.  
  2267. static OSErr DoExtractBinaries (WindowPtr wind, short modifiers)
  2268. {
  2269.     TWindow **info;
  2270.     ListHandle theList;
  2271.     TSubject **subjectArray;
  2272.     short cellDataLen, index;
  2273.     short numCells, partNum;
  2274.     Cell theCell;
  2275.     short *p, *pBegin, *pEnd, numArts = 0, artNum = 0;
  2276.     OSErr err = noErr;
  2277.     short prevThreadHeadIndex = -1, threadHeadIndex;
  2278.     short numArtsWithoutAttachedFiles = 0, numIncomplete = 0, numArtsNotOnServer = 0;
  2279.     Boolean incomplete;
  2280.     TAttachedFileKind fileKind;
  2281.     CStr255 fmt, msg;
  2282.     
  2283.     info = (TWindow**) GetWRefCon(wind);
  2284.     theList = (**info).theList;
  2285.     subjectArray = (**info).subjectArray;
  2286.     numCells = (**theList).dataBounds.bottom;
  2287.     
  2288.     SetPt(&theCell, 0, 0);
  2289.     while (theCell.v < numCells) {
  2290.         pBegin = (**theList).cellArray;
  2291.         pEnd = pBegin + numCells;
  2292.         p = pBegin + theCell.v;
  2293.         while (*p >= 0 && p < pEnd) p++;
  2294.         theCell.v = p - pBegin;
  2295.         if (p < pEnd) {
  2296.             cellDataLen = 2;
  2297.             LGetCell(&index, &cellDataLen, theCell, theList);
  2298.             incomplete = (*subjectArray)[index].incomplete;
  2299.             partNum = (*subjectArray)[index].partNum;
  2300.             threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  2301.             if (incomplete) {
  2302.                 numIncomplete++;
  2303.             } else if (partNum == 0x7fff) {
  2304.                 numArts++;
  2305.             } else if (threadHeadIndex != prevThreadHeadIndex) {
  2306.                 prevThreadHeadIndex = threadHeadIndex;
  2307.                 numArts++;
  2308.             }
  2309.         }
  2310.         theCell.v++;
  2311.     }
  2312.     
  2313.     if (numArts == 0) {
  2314.         if (numIncomplete > 0) {
  2315.             if (numIncomplete == 1) {
  2316.                 NoteMessageNumber(kStrDecodePartsMissing);
  2317.             } else {
  2318.                 NoteMessageNumber(kStrAllSelectedArtsHaveMissingParts);
  2319.             }
  2320.         }
  2321.         return noErr;
  2322.     }
  2323.     
  2324.     SetPt(&theCell, 0, 0);
  2325.     prevThreadHeadIndex = -1;
  2326.     while (theCell.v < numCells) {
  2327.         pBegin = (**theList).cellArray;
  2328.         pEnd = pBegin + numCells;
  2329.         p = pBegin + theCell.v;
  2330.         while (*p >= 0 && p < pEnd) p++;
  2331.         theCell.v = p - pBegin;
  2332.         if (p < pEnd) {
  2333.             cellDataLen = 2;
  2334.             LGetCell(&index, &cellDataLen, theCell, theList);
  2335.             threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  2336.             incomplete = (*subjectArray)[index].incomplete;
  2337.             partNum = (*subjectArray)[index].partNum;
  2338.             threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  2339.             if (!incomplete && (partNum == 0x7fff || threadHeadIndex != prevThreadHeadIndex)) {
  2340.                 artNum++;
  2341.                 err = ExtractBinaries(wind, index, artNum, numArts, modifiers, &fileKind);
  2342.                 if (err != noErr) return err;
  2343.                 if (fileKind == kNoAttachedFile) numArtsWithoutAttachedFiles++;
  2344.                 if (fileKind == kArtNotOnServer) numArtsNotOnServer++;
  2345.                 if (partNum != 0x7fff) prevThreadHeadIndex = threadHeadIndex;
  2346.             }
  2347.         }
  2348.         theCell.v++;
  2349.     }
  2350.     
  2351.     if (numArtsWithoutAttachedFiles > 0) {
  2352.         if (numArts == 1) {
  2353.             NoteMessageNumber(kStrArtHasNoAttachedFile);
  2354.         } else if (numArtsWithoutAttachedFiles == 1) {
  2355.             NoteMessageNumber(kStrOneArtSkippedNoAttachedFile);
  2356.         } else {
  2357.             GetCString(kStrNArtsSkippedNoAttachedFile, fmt);
  2358.             sprintf(msg, fmt, numArtsWithoutAttachedFiles);
  2359.             NoteMessage(msg);
  2360.         }
  2361.     }
  2362.     
  2363.     if (numArtsNotOnServer > 0) {
  2364.         if (numArts == 1) {
  2365.             NoteMessageNumber(kStrArtNotOnServer);
  2366.         } else if (numArtsNotOnServer == 1) {
  2367.             NoteMessageNumber(kStrOneArtSkippedNotOnServer);
  2368.         } else {
  2369.             GetCString(kStrNArtsSkippedNotOnServer, fmt);
  2370.             sprintf(msg, fmt, numArtsNotOnServer);
  2371.             NoteMessage(msg);
  2372.         }
  2373.     }
  2374.     
  2375.     if (numIncomplete == 1) {
  2376.         NoteMessageNumber(kStrOneArtSkippedMissingParts);
  2377.     } else if (numIncomplete > 1) {
  2378.         GetCString(kStrNArtsSkippedMissingParts, fmt);
  2379.         sprintf(msg, fmt, numIncomplete);
  2380.         NoteMessage(msg);
  2381.     }
  2382.  
  2383.     return noErr;
  2384. }
  2385.  
  2386.  
  2387.  
  2388. /*----------------------------------------------------------------------------
  2389.     DoExtractBinariesManually 
  2390.     
  2391.     Handle the "Extract Binaries Manually" command for a subject window.
  2392.             
  2393.     Entry:    wind = pointer to subject window.
  2394.             modifiers = modifiers field from event record.
  2395.     
  2396.     Exit:    function result = error code.
  2397. ----------------------------------------------------------------------------*/
  2398.  
  2399. static OSErr DoExtractBinariesManually (WindowPtr wind, short modifiers)
  2400. {
  2401.     TWindow **info;
  2402.     ListHandle theList;
  2403.     TSubject **subjectArray;
  2404.     short numCells;
  2405.     short cellDataLen, index;
  2406.     short *p, *pBegin, *pEnd, numSelected = 0;
  2407.     Handle strings;
  2408.     OSErr err = noErr;
  2409.     DialogPtr dlg = nil;
  2410.     short item, fontNum, itemType, lineHeight, listHeight;
  2411.     Handle itemHandle;
  2412.     Rect rView;
  2413.     Rect dataBounds = {0, 0, 0, 1};
  2414.     Point cSize = {0, 0};
  2415.     CStr255 subject, str;
  2416.     Cell cella, cellb;
  2417.     long number;
  2418.     long **artList = nil;
  2419.     short artNum;
  2420.     Str31 fileName;
  2421.     FSSpec fSpec;
  2422.     ScriptCode scriptTag;
  2423.     CStr255 statusMsg, statusMsgFormat;
  2424.     CStr255 groupName;
  2425.     TAttachedFileKind fileKind = kNoAttachedFile, partKind;
  2426.     long totalCellDataLength = 0;
  2427.     short refNum = 0;
  2428.     Boolean empty;
  2429.     
  2430.     gExtractBinariesManuallyList = nil;
  2431.     *fSpec.name = 0;
  2432.     
  2433.     if (!MemoryAvailable(40000)) {
  2434.         err = memFullErr;
  2435.         goto exit;
  2436.     }
  2437.     
  2438.     info = (TWindow**) GetWRefCon(wind);
  2439.     theList = (**info).theList;
  2440.     subjectArray = (**info).subjectArray;
  2441.     strings = (**info).strings;
  2442.     numCells = (**theList).dataBounds.bottom;
  2443.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  2444.  
  2445.     err = MyGetNewDialog(kExtractBinariesManuallyDlg, ok, cancel, &dlg);
  2446.     if (err != noErr) return err;
  2447.     DlgSetUserItem(dlg, kExtractBinariesManuallyLabelItem, 
  2448.         gExtractBinariesManuallyDlgLabelUserItemUPP);
  2449.     DlgSetUserItem(dlg, kExtractBinariesManuallyListItem, 
  2450.         gExtractBinariesManuallyDlgListUserItemUPP);
  2451.         
  2452.     if (gHaveDragMgr) {
  2453.         err = InstallTrackingHandler(gExtractBinariesManuallyHandleTrackingUPP,
  2454.             dlg, nil);
  2455.         if (err != noErr) goto exit;
  2456.         err = InstallReceiveHandler(gExtractBinariesManuallyHandleReceiveUPP,
  2457.             dlg, nil);
  2458.         if (err != noErr) goto exit;
  2459.     }
  2460.     
  2461.     SetPort(dlg);
  2462.     
  2463.     GetFontNumber("\pMonaco", &fontNum);
  2464.     TextFont(fontNum);
  2465.     TextSize(9);
  2466.     
  2467.     GetDialogItem(dlg, kExtractBinariesManuallyListItem, &itemType, &itemHandle, &rView);
  2468.     InsetRect(&rView, 1, 1);
  2469.     rView.right -= 15;
  2470.     lineHeight = GetFontLineHeight(dlg);
  2471.     listHeight = rView.bottom - rView.top;
  2472.     listHeight = listHeight/lineHeight*lineHeight;
  2473.     rView.bottom = rView.top + listHeight;
  2474.     gExtractBinariesManuallyList = LNew(&rView, &dataBounds, cSize, 0,
  2475.         dlg, true, false, false, true);
  2476.     (**gExtractBinariesManuallyList).indent.h = 4;
  2477.     (**gExtractBinariesManuallyList).selFlags |= lNoNilHilite;
  2478.     rView.right += 15;
  2479.     InsetRect(&rView, -1, -1);
  2480.     SetDialogItem(dlg, kExtractBinariesManuallyListItem, itemType, itemHandle, &rView);
  2481.         
  2482.     numSelected = NumListItemsSelected(theList);
  2483.     LAddRow(numSelected, 0, gExtractBinariesManuallyList);
  2484.     
  2485.     SetPt(&cella, 0, 0);
  2486.     SetPt(&cellb, 0, 0);
  2487.     while (cella.v < numCells) {
  2488.         pBegin = (**theList).cellArray;
  2489.         pEnd = pBegin + numCells;
  2490.         p = pBegin + cella.v;
  2491.         while (*p >= 0 && p < pEnd) p++;
  2492.         cella.v = p - pBegin;
  2493.         if (p < pEnd) {
  2494.             cellDataLen = 2;
  2495.             LGetCell(&index, &cellDataLen, cella, theList);
  2496.             strcpy(subject, *strings + (*subjectArray)[index].subjectOffset);
  2497.             sprintf(str, "%ld - %.240s", (*subjectArray)[index].number, subject);
  2498.             totalCellDataLength += strlen(str);
  2499.             if (totalCellDataLength > 0x7fff) {
  2500.                 ErrorMessageNumber(kStrExtractBinariesManuallyTooMuchCellData);
  2501.                 goto exit;
  2502.             }
  2503.             LSetCell(str, strlen(str), cellb, gExtractBinariesManuallyList);
  2504.             cellb.v++;
  2505.         }
  2506.         cella.v++;
  2507.     }
  2508.     
  2509.     MyModalDialog(dlg, gExtractBinariesManuallyDialogFilterUPP, &item);
  2510.     
  2511.     if (item == ok) {
  2512.         err = MyNewHandle(numSelected * sizeof(long), &artList);
  2513.         if (err != noErr) goto exit;
  2514.         SetPt(&cellb, 0, 0);
  2515.         while (cellb.v < numSelected) {
  2516.             cellDataLen = 256;
  2517.             LGetCell(str, &cellDataLen, cellb, gExtractBinariesManuallyList);
  2518.             str[cellDataLen] = 0;
  2519.             number = atol(str);
  2520.             (*artList)[cellb.v] = number;
  2521.             cellb.v++;
  2522.         }
  2523.     }
  2524.     
  2525.     LDispose(gExtractBinariesManuallyList);
  2526.     gExtractBinariesManuallyList = nil;
  2527.     
  2528.     if (gHaveDragMgr) {
  2529.         RemoveTrackingHandler(gExtractBinariesManuallyHandleTrackingUPP, dlg);
  2530.         RemoveReceiveHandler(gExtractBinariesManuallyHandleReceiveUPP, dlg);
  2531.     }
  2532.     
  2533.     err = DoClose(dlg);
  2534.     if (err != noErr) return err;
  2535.     dlg = nil;
  2536.     if (item == cancel) return userCanceledErr;
  2537.     
  2538.     GetPString(kStrTempFileName, fileName);
  2539.     
  2540.     if (gPrefs.savedBinDefaultFolder && (modifiers & optionKey) == 0) {
  2541.         err = CheckDownloadFileExists(fileName, &fSpec, &scriptTag);
  2542.         if (err != noErr) goto exit;
  2543.     } else {
  2544.         err = PresentStandardDownloadSaveFileDialog(fileName, &fSpec, &scriptTag);
  2545.         if (err != noErr) goto exit;
  2546.     }
  2547.     
  2548.     GetCString(kStrGettingAndSavingPartNofM, statusMsgFormat);
  2549.     
  2550.     MyICReadSharedPrefs(kICeditorHelper);
  2551.     
  2552.     err = OpenDataForkWriteCreateIfMissing(&fSpec, gPrefs.savedArtCreator, 'TEXT',
  2553.         scriptTag, false, &refNum, &empty);
  2554.     if (err != noErr) return err;
  2555.     
  2556.     for (artNum = 0; artNum < numSelected; artNum++) {
  2557.         sprintf(statusMsg, statusMsgFormat, artNum+1, numSelected);
  2558.         err = DisplayStatusMessage(statusMsg);
  2559.         if (err != noErr) goto exit;
  2560.         number = (*artList)[artNum];
  2561.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum, 
  2562.             false, false, &partKind);
  2563.         if (err != noErr) goto exit;
  2564.         if (partKind == kArtNotOnServer) {
  2565.             fileKind = kArtNotOnServer;
  2566.             break;
  2567.         } else if (fileKind == kNoAttachedFile) {
  2568.             fileKind = partKind;
  2569.         }
  2570.     }
  2571.     
  2572.     MyFSClose(refNum, GiveTime);
  2573.     
  2574.     MyDisposeHandle(artList);
  2575.     
  2576.     if (fileKind == kNoAttachedFile) {
  2577.         FSpDelete(&fSpec);
  2578.         NoteMessageNumber(kStrArtHasNoAttachedFile);
  2579.     } else if (fileKind == kArtNotOnServer) {
  2580.         FSpDelete(&fSpec);
  2581.         NoteMessageNumber(kStrArtNotOnServer);
  2582.     } else {
  2583.         err = RunDecodeHelperProgram(&fSpec, fileKind);
  2584.         *fSpec.name = 0;
  2585.         if (err != noErr) goto exit;
  2586.         DoMarkCommand(wind, true);
  2587.     }
  2588.     
  2589.     return noErr;
  2590.     
  2591. exit:
  2592.  
  2593.     if (gExtractBinariesManuallyList != nil) LDispose(gExtractBinariesManuallyList);
  2594.     gExtractBinariesManuallyList = nil;
  2595.     if (dlg != nil) {
  2596.         RemoveTrackingHandler(gExtractBinariesManuallyHandleTrackingUPP, dlg);
  2597.         RemoveReceiveHandler(gExtractBinariesManuallyHandleReceiveUPP, dlg);
  2598.         DoClose(dlg);
  2599.     }
  2600.     MyDisposeHandle(artList);
  2601.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  2602.     if (*fSpec.name != 0) FSpDelete(&fSpec);
  2603.     return err;
  2604. }
  2605.  
  2606.  
  2607.  
  2608. /*----------------------------------------------------------------------------
  2609.     DoCancelArticle 
  2610.     
  2611.     Handle the "Cancel Article" command for a subject window.
  2612.             
  2613.     Entry:    wind = pointer to subject window.
  2614.     
  2615.     Exit:    function result = error code.
  2616. ----------------------------------------------------------------------------*/
  2617.  
  2618. static OSErr DoCancelArticle (WindowPtr wind)
  2619. {
  2620.     Cell theCell;
  2621.     TWindow **info;
  2622.     ListHandle theList;
  2623.     short *p, *pEnd, *pBegin;
  2624.     short numSelected=0, numCanceled=0, i = 0, numCells;
  2625.     OSErr err = noErr;
  2626.     Boolean canceled;
  2627.     CStr255 msg, fmt;
  2628.     
  2629.     info = (TWindow**) GetWRefCon(wind);
  2630.     theList = (**info).theList;
  2631.     numCells = (**theList).dataBounds.bottom;
  2632.     
  2633.     numSelected = NumListItemsSelected(theList);
  2634.     if (numSelected == 0) return noErr;
  2635.     if (numSelected == 1) {
  2636.         GetCString(kStrCancelingStatusMsg, msg);
  2637.     } else {
  2638.         GetCString(kStrCancelingNofMStatusMsg, fmt);
  2639.     }
  2640.     
  2641.     SetPt(&theCell, 0, 0);
  2642.     while (theCell.v < numCells) {
  2643.         pBegin = (**theList).cellArray;
  2644.         pEnd = pBegin + numCells;
  2645.         p = pBegin + theCell.v;
  2646.         while (*p >= 0 && p < pEnd) p++;
  2647.         theCell.v = p - pBegin;
  2648.         if (p < pEnd) {
  2649.             if (numSelected != 1) {
  2650.                 i++;
  2651.                 sprintf(msg, fmt, i, numSelected);
  2652.             }
  2653.             err = DisplayStatusMessage(msg);
  2654.             if (err != noErr) return err;
  2655.             err = CancelSubjectCell(wind, theCell, msg, &canceled);
  2656.             if (err != noErr) return err;
  2657.             if (canceled) numCanceled++;
  2658.         }
  2659.         theCell.v++;
  2660.     }
  2661.     if (numCanceled < numSelected) {
  2662.         if (numSelected == 1) {
  2663.             ErrorMessageNumber(kStrNotCanceled);
  2664.         } else {
  2665.             if (numCanceled == 0) {
  2666.                 ErrorMessageNumber(kStrNoneCanceled);
  2667.             } else if (numSelected - numCanceled == 1) {
  2668.                 ErrorMessageNumber(kStrOneNotCanceled);
  2669.             } else {
  2670.                 GetCString(kStrNNotCanceled, fmt);
  2671.                 sprintf(msg, fmt, numSelected - numCanceled);
  2672.                 ErrorMessage(msg);
  2673.             }
  2674.         }
  2675.     }
  2676.     return noErr;
  2677. }
  2678.  
  2679.  
  2680.  
  2681. /*----------------------------------------------------------------------------
  2682.     Activate 
  2683.     
  2684.     Handle an activate event for a subject window.
  2685.             
  2686.     Entry:    wind = pointer to subject window.
  2687.             act = true to activate, false to deactivate
  2688. ----------------------------------------------------------------------------*/
  2689.  
  2690. static void Activate (WindowPtr wind, Boolean act)
  2691. {
  2692.     TWindow **info;
  2693.     Rect r;
  2694.     
  2695.     info = (TWindow**)GetWRefCon(wind);
  2696.     MyLActivate(act, (**info).theList);
  2697.     r = wind->portRect;
  2698.     r.top = r.bottom - 15;
  2699.     r.left = r.right - 15;
  2700.     InvalRect(&r);
  2701. }
  2702.  
  2703.  
  2704.  
  2705. /*----------------------------------------------------------------------------
  2706.     Update 
  2707.     
  2708.     Handle an update event for a subject window.
  2709.             
  2710.     Entry:    wind = pointer to subject window.
  2711. ----------------------------------------------------------------------------*/
  2712.  
  2713. static void Update (WindowPtr wind)
  2714. {
  2715.     TWindow **info;
  2716.     short panelHeight, windWidth, windHeight, numSubjects, numSubjectsInList;
  2717.     short numUnread, i;
  2718.     TSubject **subjectArray, *pSubject;    
  2719.     Rect r;
  2720.     FontInfo fontInfo;
  2721.     CStr255 str, fmt;
  2722.  
  2723.     info = (TWindow**)GetWRefCon(wind);
  2724.     panelHeight = (**info).panelHeight;
  2725.     GetFontInfo(&fontInfo);
  2726.     windWidth = wind->portRect.right;
  2727.     windHeight = wind->portRect.bottom;
  2728.     
  2729.     r = wind->portRect;
  2730.     r.top += panelHeight;
  2731.     ClipRect(&r);
  2732.     DrawGrowIcon(wind);
  2733.     ClipRect(&wind->portRect);
  2734.     
  2735.     MoveTo(0, panelHeight-3);
  2736.     LineTo(windWidth, panelHeight-3);
  2737.     MoveTo(0, panelHeight-1);
  2738.     LineTo(windWidth, panelHeight-1);
  2739.  
  2740.     numSubjects = (**info).numSubjectsInList;
  2741.     numSubjectsInList = (**info).numSubjectsInList;
  2742.     numUnread = 0;
  2743.     subjectArray = (**info).subjectArray;
  2744.     for (i = 0, pSubject = *subjectArray; i < numSubjects; i++, pSubject++) {
  2745.         if (!pSubject->read && pSubject->inList) numUnread++;
  2746.     }
  2747.     if (numSubjectsInList == 1) {
  2748.         GetCString(kStrOneArt, fmt);
  2749.         sprintf(str, fmt, numUnread);
  2750.     } else {
  2751.         GetCString(kStrNArts, fmt);
  2752.         sprintf(str, fmt, numSubjectsInList, numUnread);
  2753.     }
  2754.     c2pstr(str);
  2755.     TruncString(windWidth - 20, (StringPtr)str, smTruncEnd);
  2756.     MoveTo(10, fontInfo.ascent + 3);
  2757.     DrawString((StringPtr)str);
  2758.  
  2759.     LUpdate(wind->visRgn, (**info).theList);
  2760.     
  2761.     DrawPadlockIcon(wind);
  2762. }
  2763.  
  2764.  
  2765.  
  2766. /*----------------------------------------------------------------------------
  2767.     Mouse 
  2768.     
  2769.     Handle a mouse down event in the content area of a subject window.
  2770.             
  2771.     Entry:    wind = pointer to subject window.
  2772.             where = location of mouse down in local coords.
  2773.             modifiers = modifiers field from event record.
  2774.             
  2775.     Exit:    function result = error code.
  2776. ----------------------------------------------------------------------------*/
  2777.  
  2778. static OSErr Mouse (WindowPtr wind, Point where, short modifiers)
  2779. {
  2780.     TWindow **info;
  2781.     ListHandle theList;
  2782.     Boolean triangleClicked, shift, command;
  2783.     OSErr err = noErr;
  2784.  
  2785.     info = (TWindow**) GetWRefCon(wind);
  2786.     theList = (**info).theList;
  2787.     
  2788.     shift = (modifiers & shiftKey) != 0;
  2789.     command = (modifiers & cmdKey) != 0;
  2790.     
  2791.     if (where.v < (**info).panelHeight) {
  2792.         SelectOrDeselectAllListItems(theList, false);
  2793.         return noErr;
  2794.     }
  2795.     
  2796.     err = TriangleClick(wind, where, &triangleClicked);
  2797.     if (err != noErr) return err;
  2798.     if (triangleClicked) return noErr;
  2799.     
  2800.     if (HandlePadlockClick(wind, where, &gPrefs.subjectWindPosLocked, 
  2801.         &gPrefs.subjectWindLocn)) return noErr;
  2802.     
  2803.     if (gHaveDragMgr && !shift && !command && PtInListCell(where, theList)) {
  2804.     
  2805.         gDragSrcWindow = wind;
  2806.         gFirstListClickCall = true;
  2807.         gClickLoopErr = noErr;
  2808.         gDraggedToFinder = false;
  2809.         SetListClickLoop(theList, gSubjectListClickLoopUPP);
  2810.         if (MyLClick(where, modifiers, theList)) {
  2811.             err = OpenSelectedCells(wind);
  2812.             if (err != noErr) return err;
  2813.         } else {
  2814.             if (gClickLoopErr != noErr) return gClickLoopErr;
  2815.             if (gDraggedToFinder) {
  2816.                 err = SaveSelectedArticlesToFile(wind, &gDragTheFile, smSystemScript, false, 
  2817.                     gPrefs.saveEncodedText, gPrefs.saveThreadsToSeparateFiles, true);
  2818.                 if (err != noErr) return err;
  2819.             }
  2820.         }
  2821.     
  2822.     } else {
  2823.     
  2824.         (**theList).lClickLoop = nil;
  2825.         if (MyLClick(where, modifiers, theList) && !shift && !command) {
  2826.             err = OpenSelectedCells(wind);
  2827.             if (err != noErr) return err;
  2828.         }
  2829.     }
  2830.     
  2831.     return noErr;
  2832. }
  2833.  
  2834.  
  2835.  
  2836. /*----------------------------------------------------------------------------
  2837.     Draggable
  2838.     
  2839.     Determine whether a mouse down event is on a draggable object in a 
  2840.     subject window.
  2841.     
  2842.     Entry:    wind = pointer to subject window.
  2843.             where = location of mouse down event, in local coordinates.
  2844.             modifiers = modifiers field from event record.
  2845.             
  2846.     Exit:    function result = true if object is draggable.
  2847. ----------------------------------------------------------------------------*/
  2848.  
  2849. static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
  2850. {
  2851.     TWindow **info;
  2852.     ListHandle theList;
  2853.  
  2854.     if ((modifiers & shiftKey) != 0) return false;
  2855.     if ((modifiers & cmdKey) != 0) return false;
  2856.     info = (TWindow**)GetWRefCon(wind);
  2857.     theList = (**info).theList;
  2858.     return PtInListCell(where, theList);
  2859. }
  2860.  
  2861.  
  2862.  
  2863. /*----------------------------------------------------------------------------
  2864.     Key 
  2865.     
  2866.     Handle a key down event for a subject window.
  2867.             
  2868.     Entry:    wind = pointer to subject window.
  2869.             theChar = ASCII code of key.
  2870.             theKey = keyboard code of key.
  2871.             modifiers = modifiers field from event record.
  2872.             
  2873.     Exit:    function result = error code.
  2874. ----------------------------------------------------------------------------*/
  2875.  
  2876. static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey, 
  2877.     short modifiers)
  2878. {
  2879.     OSErr err = noErr;
  2880.     Cell scrollIntoView;
  2881.     TWindow **info;
  2882.     ListHandle theList;
  2883.     TKeypadKey keypadKey;
  2884.     Boolean command, isArrow;
  2885.  
  2886.     info = (TWindow**)GetWRefCon(wind);
  2887.     theList = (**info).theList;
  2888.     command = (modifiers & cmdKey) != 0;
  2889.     isArrow = IsArrowKey(theChar);
  2890.     
  2891.     if (command && !isArrow) {
  2892.         SysBeep(0);
  2893.         return noErr;
  2894.     }
  2895.     
  2896.     if (command && (theChar == leftArrow || theChar == rightArrow))
  2897.         return ExpandCollapseKey(wind, theChar);
  2898.  
  2899.     if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
  2900.         switch (keypadKey) {
  2901.             case kKeypadClearKey:
  2902.                 return DoMarkOthersRead(wind);
  2903.             case kKeypadEqualKey:
  2904.                 DoSelectAll(wind);
  2905.                 return noErr;
  2906.             case kKeypadSlashKey:
  2907.                 return DoExpandCollapseSelectedThread(wind);
  2908.             case kKeypadStarKey:
  2909.                 return DoClose(wind);
  2910.             case kKeypadMinusKey:
  2911.                 return DoMarkCommand(wind, false);
  2912.             case kKeypadPlusKey:
  2913.                 return DoMarkCommand(wind, true);
  2914.             case kKeypadEnterKey:
  2915.                 return DoNextGroup(wind);
  2916.             case kKeypadPeriodKey:
  2917.                 return DoNextThread(wind);
  2918.             case kKeypad0Key:
  2919.                 return DoNextArticle(wind);
  2920.             case kKeypad1Key:
  2921.                 Scroll(wind, kScrollToEnd);
  2922.                 return noErr;
  2923.             case kKeypad2Key:
  2924.                 Scroll(wind, inDownButton);
  2925.                 return noErr;
  2926.             case kKeypad3Key:
  2927.                 Scroll(wind, inPageDown);
  2928.                 return noErr;
  2929.             case kKeypad5Key:
  2930.                 return DoNextArticle(wind);
  2931.             case kKeypad7Key:
  2932.                 Scroll(wind, kScrollToHome);
  2933.                 return noErr;
  2934.             case kKeypad8Key:
  2935.                 Scroll(wind, inUpButton);
  2936.                 return noErr;
  2937.             case kKeypad9Key:
  2938.                 Scroll(wind, inPageUp);
  2939.                 return noErr;
  2940.             default:
  2941.                 SysBeep(0);
  2942.                 return noErr;
  2943.         }
  2944.     }
  2945.     
  2946.     if (theChar == pageUpKey) {
  2947.         Scroll(wind, inPageUp);
  2948.         return noErr;
  2949.     }
  2950.     if (theChar == pageDownKey) {
  2951.         Scroll(wind, inPageDown);
  2952.         return noErr;
  2953.     }
  2954.     if (theChar == homeKey) {
  2955.         Scroll(wind, kScrollToHome);
  2956.         return noErr;
  2957.     }
  2958.     if (theChar == endKey) {
  2959.         Scroll(wind, kScrollToEnd);
  2960.         return noErr;
  2961.     }
  2962.     if (theChar == returnKey || theChar == enterKey) {
  2963.         return OpenSelectedCells(wind);
  2964.     }
  2965.     
  2966.     if (theChar == upArrow || theChar == downArrow) {
  2967.         ListArrowKey(theChar, modifiers, theList, &gPrevEvent, &scrollIntoView);
  2968.         HandleUpdate(wind);
  2969.         MyLScrollCellIntoView(scrollIntoView, theList);
  2970.         return noErr;
  2971.     }
  2972.     
  2973.     if (gPrefs.keyboardShortcuts) {
  2974.     
  2975.         theChar = tolower(theChar);
  2976.         
  2977.         if (theChar == ' ' || theChar == 'n' || theChar == 'i') {
  2978.             return DoNextArticle(wind);
  2979.         }
  2980.         
  2981.         if (theChar == 't') {
  2982.             return DoNextThread(wind);
  2983.         }
  2984.         
  2985.         if (theChar == 'g' || theChar == 'j') {
  2986.             return DoNextGroup(wind);
  2987.         }
  2988.         
  2989.         if (theChar == 'w') {
  2990.             return DoClose(wind);
  2991.         }
  2992.         
  2993.         if (theChar == 'u') {
  2994.             return DoMarkCommand(wind, false);
  2995.         }
  2996.         
  2997.         if (theChar == 'm') {
  2998.             return DoMarkCommand(wind, true);
  2999.         }
  3000.         
  3001.         if (theChar == 'a') {
  3002.             DoSelectAll(wind);
  3003.             return noErr;
  3004.         }
  3005.         
  3006.         if (theChar == ';') {
  3007.             return DoMarkOthersRead(wind);
  3008.         }
  3009.         
  3010.     }
  3011.     
  3012.     SysBeep(0);
  3013.     return noErr;
  3014. }
  3015.  
  3016.  
  3017.  
  3018. /*----------------------------------------------------------------------------
  3019.     Grow 
  3020.     
  3021.     Handle a mouse down event in the grow box of a subject window.
  3022.     
  3023.     Entry:    wind = pointer to subject window.
  3024.             where = location of mouse down event, in global coordinates.
  3025.             
  3026.     Exit:    function result = error code.
  3027. ----------------------------------------------------------------------------*/
  3028.  
  3029. static OSErr Grow (WindowPtr wind, Point where)
  3030. {
  3031.     Rect sizeRect;
  3032.     long size;
  3033.     short width, height;
  3034.     
  3035.     SetRect(&sizeRect, kMinWindowWidth, MinHeight(wind), 0x7fff, 0x7fff);
  3036.     size = GrowWindow(wind, where, &sizeRect);
  3037.     
  3038.     if (size  != 0) {
  3039.         width = LoWord(size);
  3040.         height = HiWord(size);
  3041.         FixHeight(wind, &height);
  3042.         SizeWindow(wind, width, height, false);
  3043.         ResizeContents(wind);
  3044.     }
  3045.     
  3046.     return noErr;
  3047. }
  3048.  
  3049.  
  3050.  
  3051. /*----------------------------------------------------------------------------
  3052.     Zoom
  3053.     
  3054.     Zoom a subject window.
  3055.     
  3056.     Entry:    wind = pointer to subject window.
  3057.             zoomDir = zoom direction = inZoomIn or inZoomOut.
  3058.             
  3059.     Exit:    function result = error code.
  3060. ----------------------------------------------------------------------------*/
  3061.  
  3062. static OSErr Zoom (WindowPtr wind, short zoomDir)
  3063. {
  3064.     TWindow **info;
  3065.     ListHandle theList;
  3066.     TSubject **subjectArray, *pSubjectArray;
  3067.     Handle strings;
  3068.     short width, height, lineHeight, minHeight, panelHeight, i, len;
  3069.     short subjectWidth, maxSubjectWidth, numCells, numSubjects;
  3070.     char *subject;
  3071.     Rect zoomRect;    
  3072.     WStateData **wState;
  3073.     long longHeight;
  3074.     char state1, state2;
  3075.  
  3076.     wState = (WStateData**)((WindowPeek)wind)->dataHandle;
  3077.     if (zoomDir == inZoomOut) {
  3078.         info = (TWindow**)GetWRefCon(wind);
  3079.         theList = (**info).theList;
  3080.         panelHeight = (**info).panelHeight;
  3081.         subjectArray = (**info).subjectArray;
  3082.         strings = (**info).strings;
  3083.         numCells = (**theList).dataBounds.bottom;
  3084.         lineHeight = (**theList).cellSize.v;
  3085.         maxSubjectWidth = (**info).maxSubjectWidth;
  3086.         numSubjects = (**info).numSubjects;
  3087.         
  3088.         if (maxSubjectWidth == 0) {
  3089.             state1 = MyHGetState(strings);
  3090.             state2 = MyHGetState(subjectArray);
  3091.             MyHLock(strings);
  3092.             MyHLock(subjectArray);
  3093.             for (i = 0, pSubjectArray = *subjectArray; i < numSubjects; i++, pSubjectArray++) {
  3094.                 if (pSubjectArray->inList) {
  3095.                     subject = *strings + pSubjectArray->subjectOffset;
  3096.                     len = strlen(subject);
  3097.                     if (len > 80) len = 80;
  3098.                     subjectWidth = TextWidth(subject, 0, len);
  3099.                     if (subjectWidth > maxSubjectWidth) maxSubjectWidth = subjectWidth;
  3100.                 }
  3101.             }
  3102.             MyHSetState(strings, state1);
  3103.             MyHSetState(subjectArray, state2);
  3104.         }
  3105.         width = maxSubjectWidth + (**info).subjectHCoord + 24;
  3106.         if (width < kMinWindowWidth) width = kMinWindowWidth;
  3107.         
  3108.         longHeight = (long)numCells * (long)lineHeight + panelHeight + 15;
  3109.         if (longHeight > 0x7fff) {
  3110.             height = 0x7fff;
  3111.         } else {
  3112.             height = longHeight;
  3113.             minHeight = MinHeight(wind);
  3114.             if (height < minHeight) height = minHeight;
  3115.         }
  3116.  
  3117.         CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
  3118.         height = zoomRect.bottom - zoomRect.top;
  3119.         FixHeight(wind, &height);
  3120.         zoomRect.bottom = zoomRect.top + height;
  3121.         (**wState).stdState = zoomRect;
  3122.         if (WindRectEqualRect(wind, &zoomRect)) return noErr;
  3123.     }
  3124.     
  3125.     EraseRect(&wind->portRect);
  3126.     ZoomWindow(wind, zoomDir, false);
  3127.     ResizeContents(wind);
  3128.     return noErr;
  3129. }
  3130.  
  3131.  
  3132.  
  3133. /*----------------------------------------------------------------------------
  3134.     Command 
  3135.     
  3136.     Handle a command for a subject window.
  3137.             
  3138.     Entry:    wind = pointer to subject window.
  3139.             menu = the menu.
  3140.             item = the item.
  3141.             modifiers = modifiers field from event record.
  3142.     
  3143.     Exit:    function result = error code.
  3144. ----------------------------------------------------------------------------*/
  3145.  
  3146. static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
  3147. {
  3148.     OSErr err = noErr;
  3149.  
  3150.     switch (menu) {
  3151.             
  3152.         case kFileMenu:
  3153.  
  3154.             switch (item) {
  3155.                 case kSaveItem:
  3156.                     err = DoSave(wind, modifiers);
  3157.                     break;
  3158.                 case kSaveAsItem:
  3159.                     err = DoSaveAs(wind);
  3160.                     break;
  3161.                 case kAppendItem:
  3162.                     err = DoAppend(wind);
  3163.                     break;
  3164.                 case kPrintItem:
  3165.                     err = DoPrint(wind);
  3166.                     break;
  3167.             }
  3168.             break;
  3169.             
  3170.         case kEditMenu:
  3171.  
  3172.             switch (item) {
  3173.                 case kSelectAllItem:
  3174.                     DoSelectAll(wind);
  3175.                     break;
  3176.                 case kDeselectAllItem:
  3177.                     DoDeselectAll(wind);
  3178.                     break;
  3179.                 case kFindItem:
  3180.                     err = DoFind(wind);
  3181.                     break;
  3182.                 case kFindAgainItem:
  3183.                     err = DoFindAgain(wind);
  3184.                     break;
  3185.             }
  3186.             break;
  3187.  
  3188.         case kNewsMenu:
  3189.         
  3190.             switch (item) {
  3191.                 case kNextArticleItem:
  3192.                     err = DoNextArticle(wind);
  3193.                     break;
  3194.                 case kNextThreadItem:
  3195.                     err = DoNextThread(wind);
  3196.                     break;
  3197.                 case kNextGroupItem:
  3198.                     err = DoNextGroup(wind);
  3199.                     break;
  3200.                 case kMarkReadItem:
  3201.                     err = DoMarkCommand(wind, true);
  3202.                     break;
  3203.                 case kMarkUnreadItem:
  3204.                     err = DoMarkCommand(wind, false);
  3205.                     break;
  3206.                 case kMarkOthersReadItem:
  3207.                     err = DoMarkOthersRead(wind);
  3208.                     break;
  3209.                 case kReplyItem:
  3210.                     err = DoReply(wind, modifiers);
  3211.                     break;
  3212.                 case kForwardItem:
  3213.                     err = DoForward(wind, modifiers);
  3214.                     break;
  3215.                 case kRedirectItem:
  3216.                     err = DoRedirect(wind, modifiers);
  3217.                     break;
  3218.             }
  3219.             break;
  3220.             
  3221.         case kSpecialMenu:
  3222.         
  3223.             switch (item) {
  3224.                 case kExtractBinariesItem:
  3225.                     err = DoExtractBinaries(wind, modifiers);
  3226.                     break;
  3227.                 case kExtractBinariesManuallyItem:
  3228.                     err = DoExtractBinariesManually(wind, modifiers);
  3229.                     break;
  3230.                 case kSearchItem:
  3231.                     err = DoSearch(wind);
  3232.                     break;
  3233.                 case kCancelArticleItem:
  3234.                     err = DoCancelArticle(wind);
  3235.                     break;
  3236.             }
  3237.      }
  3238.      
  3239.      return err;
  3240. }
  3241.  
  3242.  
  3243.  
  3244. /*----------------------------------------------------------------------------
  3245.     Close 
  3246.     
  3247.     Close a subject window.
  3248.             
  3249.     Entry:    wind = pointer to subject window.
  3250.     
  3251.     Exit:    function result = error code.
  3252. ----------------------------------------------------------------------------*/
  3253.  
  3254. static OSErr Close (WindowPtr wind)
  3255. {
  3256.     TWindow **info;
  3257.     OSErr err = noErr;
  3258.  
  3259.     info = (TWindow**)GetWRefCon(wind);
  3260.     
  3261.     while ((**info).childList != nil) {
  3262.         err = DoClose((**(**info).childList).childWindow);
  3263.         if (err != noErr) return err;
  3264.     }
  3265.     RemoveChild((**info).parentWindow, wind);
  3266.        
  3267.     err = UpdateUnreadList(wind);
  3268.     if (err != noErr) return err;
  3269.     
  3270.     if ((**info).theList != nil) LDispose((**info).theList);
  3271.     MyDisposeHandle((**info).subjectArray);
  3272.     MyDisposeHandle((**info).sortByNumber);
  3273.     MyDisposeHandle((**info).strings);
  3274.     if ((**info).collapseTriangle != nil) KillPoly((**info).collapseTriangle);
  3275.     if ((**info).expandTriangle != nil) KillPoly((**info).expandTriangle);
  3276.     MyDisposeHandle(info);
  3277.  
  3278.     MyDisposeWindow(wind);
  3279.     return noErr;
  3280. }
  3281.  
  3282.  
  3283.  
  3284. /*----------------------------------------------------------------------------
  3285.     Idle 
  3286.     
  3287.     Handle idle time tasks for a subject window.
  3288.             
  3289.     Entry:    wind = pointer to subject window.
  3290.     
  3291.     Exit:    cursorRgn = cursor region for WaitNextEvent mouse moved events.
  3292. ----------------------------------------------------------------------------*/
  3293.  
  3294. static void Idle (WindowPtr wind, RgnHandle cursorRgn)
  3295. {
  3296.     TWindow **info;
  3297.     ListHandle theList;
  3298.     unsigned long fileEnabled, editEnabled, newsEnabled, specialEnabled;
  3299.  
  3300.     info = (TWindow**)GetWRefCon(wind);
  3301.     theList = (**info).theList;
  3302.     SetCursor(&qd.arrow);
  3303.     fileEnabled = kSubjectFileEnabled;
  3304.     editEnabled = kSubjectEditEnabled;
  3305.     newsEnabled = kSubjectNewsEnabled;
  3306.     specialEnabled = kSubjectSpecialEnabled;
  3307.     if ((**theList).dataBounds.bottom == 0)
  3308.         editEnabled &= ~kSelectAllMask;
  3309.     if (!ListHasSelectedCell(theList)) {
  3310.         editEnabled &= ~kDeselectAllMask;
  3311.         fileEnabled &= ~(kSaveMask | kSaveAsMask | kAppendMask | kPrintMask);
  3312.         newsEnabled &= ~(kMarkReadMask | kMarkUnreadMask | kMarkOthersReadMask |
  3313.             kReplyMask | kForwardMask | kRedirectMask);
  3314.         specialEnabled &= ~(kExtractBinariesMask | 
  3315.             kExtractBinariesManuallyMask | kCancelArticleMask);
  3316.     }
  3317.     if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
  3318.     SetMenusTo(kAppleAllEnabled, fileEnabled, editEnabled, 
  3319.         newsEnabled, specialEnabled, kSubjectWindEnabled);
  3320. }
  3321.  
  3322.  
  3323.  
  3324. /*----------------------------------------------------------------------------
  3325.     InitSubjectDispatchTable 
  3326.     
  3327.     Initialize the dispatch table for subject windows.
  3328. ----------------------------------------------------------------------------*/
  3329.  
  3330. void InitSubjectDispatchTable (void)
  3331. {
  3332.     TDispatch *d;
  3333.     
  3334.     d = &gDispatch[kSubject];
  3335.     
  3336.     d->activate = Activate;
  3337.     d->update = Update;
  3338.     d->mouse = Mouse;
  3339.     d->draggable = Draggable;
  3340.     d->key = Key;
  3341.     d->grow = Grow;
  3342.     d->zoom = Zoom;
  3343.     d->command = Command;
  3344.     d->close = Close;
  3345.     d->idle = Idle;
  3346.  
  3347.     gExtractBinariesManuallyClickLoopUPP = 
  3348.         NewListClickLoopProc(ExtractBinariesManuallyClickLoop);
  3349.     gOldListClickLoopUPP =
  3350.         NewListClickLoopProc(OldListClickLoop);
  3351.     gSubjectListClickLoopUPP =
  3352.         NewListClickLoopProc(SubjectListClickLoop);
  3353.     gExtractBinariesManuallyHandleTrackingUPP =
  3354.         NewDragTrackingHandlerProc(ExtractBinariesManuallyHandleTracking);
  3355.     gExtractBinariesManuallyHandleReceiveUPP =
  3356.         NewDragReceiveHandlerProc(ExtractBinariesManuallyHandleReceive);
  3357.     gDragSubjectsSendProcUPP = 
  3358.         NewDragSendDataProc(DragSubjectsSendProc);
  3359.     gExtractBinariesManuallyDialogFilterUPP =
  3360.         NewModalFilterProc(ExtractBinariesManuallyDialogFilter);
  3361.     gExtractBinariesManuallyDlgLabelUserItemUPP =
  3362.         NewUserItemProc(ExtractBinariesManuallyDlgLabelUserItem);
  3363.     gExtractBinariesManuallyDlgListUserItemUPP =
  3364.         NewUserItemProc(ExtractBinariesManuallyDlgListUserItem);
  3365. }
  3366.